I have a JSON object that gets loaded as [String: Any]. I want to break down each part and turn the "Any" into an actual class. Here's one section of the JSON that I'm starting with:
"objects": {
"Yellow Fruits" = {
name = "Bananas";
numbers = (
0,
1
);
};
"Red Fruits" = {
name = "Strawberries";
numbers = (
2,
4,
5,
14,
15,
16,
17
);
};
}
I've tried representing these "fruits" with the following class:
class Fruit {
var name: String?
var officeIndicies: [Int]?
}
When I try to load this with
guard let fruits = json["objects"] as? [String: Fruit]
it seems that the Fruit class does not accurately represent what is in the json.
Is there something obvious I'm missing?
Related
I have a large JSON response and inside
{
"availabilityResultList": [
{
"availabilityRouteList": [
{
"availabilityByDateList": [
{
"originDestinationOptionList": [
there are 3 separate
"originDestinationOptionList": [
{
"fareComponentGroupList":[...]
},
{
"fareComponentGroupList":[...]
},
{
"fareComponentGroupList":[...]
},
],
I can access the values in the first 'fareComponentGroupList' with Codables
as
root.availabilityResultList.first?.availabilityRouteList.first?.availabilityByDateList.first?.originDestinationOptionList.first?.fareComponentGroupList.first?.xxx
How do I access values the second and third fareComponentGroupList ?
(I am sorry about these silly questions but I am new with swift Codables)
Since originDestinationOptionList returns an array of dictionary, just fetch it from there by index.
let originDestinationOptionList = root.availabilityResultList.first?.availabilityRouteList.first?.availabilityByDateList.first?.originDestinationOptionList
let firstobject = originDestinationOptionList[0]["fareComponentGroupList"]
let secondObject = originDestinationOptionList[1]["fareComponentGroupList"]
let firstObjectsFirstItem = firstObject[0]
If the above gives error, this works ( Swift 5)
let originDestinationOptionList = root.availabilityResultList.first?.availabilityRouteList.first?.availabilityByDateList.first?.originDestinationOptionList
let firstobject = originDestinationOptionList[0].fareComponentGroupList.first
let firstObjectsFirstItem = firstObject?. (add the remaining part)
I have a Realm model class as below.
class Trip: Object {
dynamic var userId: String = ""
dynamic var id: Int = 0
dynamic var startTimestamp: Int64 = 0
dynamic var endTimestamp: Int64 = 0
let bumpLocations = List<BumpLocation>()
let brakeLocations = List<BrakeLocation>()
dynamic var distance: Double = 0.0
dynamic var calories: Double = 0.0
dynamic var averageSpeed: Double = 0.0
}
I create objects of this like this.
let tripData: [String: Any] = [
"userId": self.trip!.userId,
"id": self.trip!.id,
"startTimestamp": self.trip!.startTimestamp,
"endTimestamp": self.trip!.endTimestamp,
"distance": self.trip!.distance,
"calories": self.trip!.calories,
"averageSpeed": self.trip!.averageSpeed
]
realm.create(Trip.self, value: tripData, update: true)
At the time of creating these objects, there are no values to be added to the properties bumpLocations and brakeLocations. So I need to add default values for these fields, right?
What's the default value for properties of type List?
I get a crash at realm.create. I think this is because the create function expects all the properties of the model class to be listed in the dictionary the same way as it is specified in the class. And since it's missing bumpLocations and brakeLocations, it messes up the order the class is created from the value dictionary? As in it adds the value of the distance field to the bumpLocations property and calories value to the brakeLocations property and so on.
The crash reports I got.
Report #1
Report 2
Got this output in the debug console.
No, you don't need to add a default values for the two lists, since they already have the default value of an empty List defined in your Realm object class definition.
In the line let bumpLocations = List<BumpLocation>() the parentheses create are the shorthand notation for calling an init method without any input arguments, so this line actually assigns an empty List to the bumpLocations variable.
class Trip: Object {
...
let bumpLocations = List<BumpLocation>()
let brakeLocations = List<BrakeLocation>()
}
Below code is perfectly valid.
let tripData: [String: Any] = [
"userId": self.trip!.userId,
"id": self.trip!.id,
"startTimestamp": self.trip!.startTimestamp,
"endTimestamp": self.trip!.endTimestamp,
"distance": self.trip!.distance,
"calories": self.trip!.calories,
"averageSpeed": self.trip!.averageSpeed,
]
try! realm.write {
realm.create(Trip.self, value: tripData)
}
{
"item": [
{
"pid": 89334,
"productsname": "Long Way",
"address": "B-4/7, Malikha Housing, Yadanar St., Bawa Myint Ward,",
"telephone": "[\"01570269\",\"01572271\"]"
},
{
"pid": 2,
"productsname": "Myanmar Reliance Energy Co., Ltd. (MRE)",
"address": "Bldg, 2, Rm# 5, 1st Flr., Hninsi St., ",
"telephone": "[\"202916\",\"09-73153580\"]"
}
],
"success": true
}
I cannot parse telephone value from above JSON object with following code.
for item in swiftyJsonVar["item"].array! {
if let jsonDict = item.dictionary {
let pid = jsonDict["pid"]!.stringValue
let productsname = jsonDict["productsname"]!.stringValue
var telephones = [String]()
for telephone in (jsonDict["telephone"]?.array)! {
telephones.append(telephone.stringValue)
}
}
}
I want to get and display one by one phone number of above JSON. I'm not sure why above code is not working. Please help me how to solve it, thanks.
Because telephone is a string that looks like an array, not an array itself. The server encoded this array terribly. You need to JSON-ify it again to loop through the list of telephone numbers:
for item in swiftyJsonVar["item"].array! {
if let jsonDict = item.dictionary {
let pid = jsonDict["pid"]!.stringValue
let productsname = jsonDict["productsname"]!.stringValue
var telephones = [String]()
let telephoneData = jsonDict["telephone"]!.stringValue.data(using: .utf8)!
let telephoneJSON = JSON(data: telephoneData)
for telephone in telephoneJSON.arrayValue {
telephones.append(telephone.stringValue)
}
}
}
Sorry for the complex wording of the question. My main experience is with PHP and it has a command called array_multisort. The syntax is below:
bool array_multisort ( array &$array1 [, mixed $array1_sort_order = SORT_ASC [, mixed $array1_sort_flags = SORT_REGULAR [, mixed $... ]]] )
It lets you sort 1 array and the reorder multiple other arrays based on the key changes in the original.
Is there an equivalent command in Swift / Xcode 7.2?
I have currently have a set of arrays:
FirstName
Age
City
Country
Active
Active is an array of time in seconds that a user has been active within my app. I would like to order that descending or ascending and the other arrays to change to remain consistent.
You could create an array of indexes in sorted order and use it as a mapping:
var names = [ "Paul", "John", "David" ]
var ages = [ 35, 42, 27 ]
let newOrder = names.enumerate().sort({$0.1<$1.1}).map({$0.0})
names = newOrder.map({names[$0]})
ages = newOrder.map({ages[$0]})
[EDIT] Here's an improvement on the technique :
It's the same approach but does the sorting and assignment in one step.
(can be reassigned to original arrays or to separate ones)
(firstNames,ages,cities,countries,actives) =
{(
$0.map{firstNames[$0]},
$0.map{ages[$0]},
$0.map{cities[$0]},
$0.map{countries[$0]},
$0.map{actives[$0]}
)}
(firstNames.enumerated().sorted{$0.1<$1.1}.map{$0.0})
[EDIT2] and an Array extension to make it even easier to use if you are sorting in place:
extension Array where Element:Comparable
{
func ordering(by order:(Element,Element)->Bool) -> [Int]
{ return self.enumerated().sorted{order($0.1,$1.1)}.map{$0.0} }
}
extension Array
{
func reorder<T>(_ otherArray:inout [T]) -> [Element]
{
otherArray = self.map{otherArray[$0 as! Int]}
return self
}
}
firstNames.ordering(by: <)
.reorder(&firstNames)
.reorder(&ages)
.reorder(&cities)
.reorder(&countries)
.reorder(&actives)
combining the previous two:
extension Array
{
func reordered<T>(_ otherArray:[T]) -> [T]
{
return self.map{otherArray[$0 as! Int]}
}
}
(firstNames,ages,cities,countries,actives) =
{(
$0.reordered(firstNames),
$0.reordered(ages),
$0.reordered(cities),
$0.reordered(countries),
$0.reordered(actives)
)}
(firstNames.ordering(by:<))
I would go with #AntonBronnikov suggestion, and put all your properties into an struct, making an Array of that particular struct and then sorting it.
This data is clearly related and it's a cleaner approach.
Edit this is valid for 2 arrays:
Adding to #AlainT answer, but using zip:
var names = [ "Paul", "John", "David" ]
var ages = [ 35, 42, 27 ]
let sortedTuple = zip(names, ages).sort { $0.0.0 < $0.1.0 }
Something more generic:
names.enumerate().sort({$0.1<$1.1}).map({ (name: $0.1, age: ages[$0.0]) })
I believe AlainT:s solution is to prefer, but to extend the variety of options, below follows a solution mimicking what a zip5 method could let us achive (in case we could use zip for zipping together 5 sequences instead of its limit of 2):
/* example arrays */
var firstName: [String] = ["David", "Paul", "Lisa"]
var age: [Int] = [17, 27, 22]
var city: [String] = ["London", "Rome", "New York"]
var country: [String] = ["England", "Italy", "USA"]
var active: [Int] = [906, 299, 5060]
/* create an array of 5-tuples to hold the members of the arrays above.
This is an approach somewhat mimicking a 5-tuple zip version. */
var quinTupleArr : [(String, Int, String, String, Int)] = []
for i in 0..<firstName.count {
quinTupleArr.append((firstName[i], age[i], city[i], country[i], active[i]))
}
/* sort w.r.t. 'active' tuple member */
quinTupleArr.sort { $0.4 < $1.4 }
/* map back to original arrays */
firstName = quinTupleArr.map {$0.0}
age = quinTupleArr.map {$0.1}
city = quinTupleArr.map {$0.2}
country = quinTupleArr.map {$0.3}
active = quinTupleArr.map {$0.4}
var sourceEntries: [Entry] = [entry1, ..., entry14]
var myDict: Dictionary<String, [Entry]> = [:]
for entry in sourceEntries {
if var array = myDict[entry.attribute1] { theArray.append(entry) }
else { myDict[entry.attribute1] = [entry] }
}
I am intending to create a Dictionary, which matches all the objects of the struct "Eintrag" with the same attribute from the source-Array "alleEinträge" to a String containing the value of the shared attribute. For some reason my final Dictionary just matches Arrays of one element to the Strings, although some Arrays ought to contain up to four elements.
The problem is that the array is passed by value (i.e. "copied"), so the array you are writing to when you say array.append is not the array that is "inside" the dictionary. You have to write back into the dictionary explicitly if you want to change what's in it.
Try it in a simple situation:
var dict = ["entry":[0,1,2]]
// your code
if var array = dict["entry"] { array.append(4) }
// so what happened?
println(dict) // [entry: [0, 1, 2]]
As you can see, the "4" never got into the dictionary.
You have to write back into the dictionary explicitly:
if var array = dict["entry"] { array.append(4); dict["entry"] = array }
FURTHER THOUGHTS: You got me thinking about whether there might be a more elegant way to do what you're trying to do. I'm not sure whether you will think this is "more elegant", but perhaps it has some appeal.
I will start by setting up a struct (like your Entry) with a name attribute:
struct Thing : Printable {
var name : String
var age : Int
var description : String {
return "{\(self.name), \(self.age)}"
}
}
Now I will create an array like your sourceEntries array, where some of the structs share the same name (like your shared attribute attribute1):
let t1 = Thing(name: "Jack", age: 40)
let t2 = Thing(name: "Jill", age: 38)
let t3 = Thing(name: "Jill", age: 37)
let arr = [t1,t2,t3]
And of course I will prepare the empty dictionary, like your myDict, which I call d:
var d = [String : [Thing]]()
Now I will create the dictionary! The idea is to use map and filter together to do all the work of creating key-value pairs, and then we just build the dictionary from those pairs:
let pairs : [(String, [Thing])] = arr.map {
t in (t.name, arr.filter{$0.name == t.name})
}
for pair in pairs { d[pair.0] = pair.1 }