Swift. Mapping from JSON to object model. Array of Array - ios

There's model in JSON format
{
"offline": false,
"data": {
"path": [
[ {
"Latitude": 56.789351316653,
"Longitude": 60.6053340947616
}, {
"Latitude": 56.78674,
"Longitude": 60.60613
}
], [ {
"Latitude": 56.79071,
"Longitude": 60.60492
}, {
"Latitude": 56.79129,
"Longitude": 60.60493
} ]
] } }
and object model on swift
http://pastebin.com/j0mK8eYG
Issue: can't parse the path field of json, because there is an array of arrays.
In the case of arrays with single dimension all works well.

I had to do something similar. I looked at the implementation of mtl_JSONArrayTransformerWithModelClass which transforms an array of dictionaries to and from an array of MTLModels. So I made a similar transformer which expects an array of array of dictionaries/MTLModels. I iterate over the outermost array and transform each array of dictionaries with the mtl_JSONArrayTransformerWithModelClass.
+ (NSValueTransformer *)JSONArrayOfArraysTransformerWithModelClass:(Class)modelClass
NSValueTransformer *arrayTransformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:modelClass];
return [MTLValueTransformer
reversibleTransformerWithForwardBlock:^id(NSArray *arrays) {
if (arrays == nil) return nil;
NSAssert([arrays isKindOfClass:NSArray.class], #"Expected an array, got: %#", arrays);
NSMutableArray *modelArrays = [NSMutableArray arrayWithCapacity:[arrays count]];
for (id JSONArray in arrays) {
if (JSONArray == NSNull.null) {
[modelArrays addObject:JSONArray];
continue;
}
NSAssert([JSONArray isKindOfClass:NSArray.class], #"Expected an array of arrays of dictionaries, got array of: %#", JSONArray);
NSArray *modelArray = [arrayTransformer transformedValue:JSONArray];
if (modelArray == nil) continue;
[modelArrays addObject:modelArray];
}
return modelArrays;
}
reverseBlock:^id(NSArray *arrays) {
if (arrays == nil) return nil;
NSAssert([arrays isKindOfClass:NSArray.class], #"Expected an array of arrays of MTLModels, got: %#", arrays);
NSMutableArray *modelArrays = [NSMutableArray arrayWithCapacity:modelArrays.count];
for (id modelArray in arrays) {
if (modelArray == NSNull.null) {
[modelArrays addObject:NSNull.null];
continue;
}
NSAssert([modelArray isKindOfClass:NSArray.class], #"Expected an array of arrays, got array of: %#", modelArray);
NSArray *array = [arrayTransformer reverseTransformedValue:modelArray];
if (array == nil) continue;
[modelArrays addObject:array];
}
return modelArrays;
}];
}

1 - Generate the Swift models by copying the same Json at http://www.json4swift.com
2 - Copy the generated files to your project
3 - Open Data.swift, replace the following method
required public init?(dictionary: NSDictionary) {
if (dictionary["path"] != nil) { path = Path.modelsFromDictionaryArray(dictionary["path"] as! NSArray) }
}
to
required public init?(dictionary: NSDictionary) {
if let paths = dictionary["path"] as? NSArray {
let allPaths = NSMutableArray()
for patharr in paths {
allPaths.addObjectsFromArray(Path.modelsFromDictionaryArray(patharr as! NSArray))
}
}
}
4 - Success!

If you want to use a third party : There is this awesome and cleanly implemented open source objectMapper, works with Alamofire as well.
Create models implementing protocol "Mappable" and custom arrays will be handled according to the Class specified.
Reduces boiler plate code like anything.
If you want to parse it on your own : Simply create a helper method in your mdoel class to parse it using for loop.
init (responseDict: [String: Any]) {
var itemModels : [ItemClass] = []
let itemDictArray = responseDict["items"] as! [[String:Any]]
for itemDict in itemDictArray {
itemModels.append(ItemClass.init(dict: (itemDict as [String:Any]) ))
}
}

Related

How to get a value from a dictionary using objectForInsensitiveKey in Swift?

I had a function in Objective c to get a value of a dictionary passing a key with insensitive key.
My function in Objective c is:
-(UIFont *) languageFont {
NSDictionary * users = #{#"Aaron" : #"English", #"Alice" : #"English", #"John" : #"Brasilian"};
NSString * countryLaunguage = [users objectForInsensitiveKey:#"Alice"];
return countryLaunguage;
}
How could I translate this function to Swift? Because in a Dictionary I can't find a similar function that returns a value from key?
Thanks!
Have a extension of Dictionary and use the feature.
extension Dictionary where Key == String {
subscript(insensitive key: Key) -> Value? {
get {
if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
return self[k]
}
return nil
}
set {
if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
self[k] = newValue
} else {
self[key] = newValue
}
}
}
}
Example:
var dict = ["Aaron" : "English", "Alice" : "English", "John" : "Brasilian"]
print(dict[insensitive: "alice"]!) // outputs "English"
Hope this answers your query.

Retrieve JSON Object and populate array Swift

I have a json object of this structure:
[
{
"id": 1,
"seat_no": 6
},
{
"id": 2,
"seat_no": 27
}
]
The main challenge is that I need to get the seat_no and add that to an int array which I will be using later on:
func getReserved() -> [Int] {
var reservedSeatsJSON : JSON = JSON()
var seats = Int()
var reservedSeats = [Int]()
for item in reservedSeatsJSON.array! {
seats = item["seat_no"].int!
reservedSeats.append(seats)
self.reservedSeatsLabel.text = "Reserved(\(reservedSeatsJSON.array!.count))"
}
return reservedSeats
}
Each time I run this, the reservedSeats returns empty. The main idea here is that I need to populate an int array in a for loop and return the populated array outside the for loop
First check is reservedSeatsJSON json contains actual JSON?
if it contains actual JSON then do as like below. short and simple way.
func getReserved() -> [Int] {
var reservedSeatsJSON : JSON = JSON()
self.reservedSeatsLabel.text = "Reserved(\(reservedSeatsJSON.array.count))"
return reservedSeatsJSON.arrayValue.map { $0["seat_no"].intValue }
}

swift parse json as per maintaining order

Suppose i have json string in which there is a json array called data.
The array holds json object of user profile data for example name,age,gender etc.
Now want to parse that json object as per order, for example if the object is
{
"name": "sample name",
"age": "30",
"gender": "male"
}
i want to parse the list as ordered like name,age,gender but with ios,when i convert the json object as dictionary , the order is changed,i know dictionary is not ordered so what is the the alternative to achieve this?
its a third party api so i dont have any hand on it,we have done it in android with linked hash map,but really stuck in swift , the last thing i would want to do is parse with regular expression.
im parsing the json in following way :
var rootData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
if let val = fromList["data"] {
let dataNode = val as! [[String:Any]]
for row in dataNode {
for (key,keyVal) in row {
//here the key is not in order.because when we cast it as dictionary the order gets changed.
}
}
For android we have achieved to do this with following function :
public ArrayList<LinkedHashMap<String, Object>> parseJsonArrayList(String odata, String arrayName) {
ArrayList<LinkedHashMap<String, Object>> mylist = new ArrayList<>();
try {
JSONObject e = new JSONObject(odata);
JSONArray data = e.getJSONArray(arrayName);
for(int i = 0; i < data.length(); ++i) {
JSONObject v = data.getJSONObject(i);
LinkedHashMap<String, Object> map = new LinkedHashMap<>(100, 0.75f, false);
Iterator keys = v.keys();
while(keys.hasNext()) {
String key = String.valueOf(keys.next());
//gph.log("debug4", key);
map.put(key, v.getString(key));
//gph.log("debug4", v.getString(key));
}
mylist.add(map);
}
} catch (JSONException var10) {
var10.printStackTrace();
}
return mylist;
}
Don’t try to order the dictionary. Instead create an array of the keys in the order you desire:
let keys = [“name”, “age”, “gender”]
Then access the dictionary with them:
for key in keys {
let value = dict[key]
// Present the value.
}
That will ensure the order you expect.
As you mentioned you cannot get the ordered data in Dictionary. If possible you can add the "order" key in your JSON like
[
{
"name": "sample name",
"order": 1
},
{
"age": "30",
"order": 1
},
{
"gender": "",
"male": "",
"order": 1
}
]
so that based on the order key you can do the sorting.

Converting objective-c codes to swift 4

it's been 1.5 years i've been writing in obj-c, its a good language.. so i saw a medium article today about swift, really looking onto it, right now i am trying to convert all my obj-c codes to swift, fortunately everything is done expect for this part..
NSString *path = [[NSBundle mainBundle] pathForResource:#"list" ofType:#"plist"];
NSArray *plistData = [NSArray arrayWithContentsOfFile:path];
NSPredicate *filter = [NSPredicate predicateWithFormat:#"english ==[c] %#", self.userQuery.text]; // something like "Abbey"
NSArray *filtered = [plistData filteredArrayUsingPredicate:filter];
NSLog(#"found matches: %# : %#", filtered,[filtered valueForKey:#"kurdi"]);
if (filtered.count>0) {
NSDictionary *dic = filtered[0];
self.theMeaningLabel.text = dic[#"kurdi"];
} else {
self.theMeaningLabel.text = #"Yay!‌";
}
i am not being able to properly convert this into the new swift 4, it gives random errors
EDIT
after a few searches, i could just write two lines of code
here's my swift code
var path: String? = Bundle.main.path(forResource: "list", ofType: "plist")
var plistData = [Any](contentsOfFile: path!)
var filter = NSPredicate(format: "english ==[c] %#", searchText)
// something like "Abbey"
var filtered = plistData?.filter { filter.evaluate(with: $0) }
print("found matches: \(filtered) : \(filtered?.value(forKey: "kurdi"))")
if filtered?.count > 0 {
var dic = filtered[0]
// theMeaningLabel.text = dic["kurdi"]
}
else {
//theMeaningLabel.text = "Yay!‌"
}
but getting the
Argument labels '(contentsOfFile:)' do not match any available overloads
Final edit
var path = Bundle.main.path(forResource:"list", ofType: "plist")
var plistData = NSArray(contentsOfFile: path!)
let filter = NSPredicate(format: "english ==[c] %#", searchText)
var filtered = plistData?.filtered(using: filter)
// [error 9:10] no viable alternative at input 'NSLog(#"found matches: %# : %#"'
if filtered?.count > 0 {
var dic = filtered![0]
// theMeaningLabel.text = dic["kurdi"]
}
else {
// theMeaningLabel.text = "Yay!‌"
}
the code above is fine, but one error comes
if filtered?.count > 0 { // here
var dic = filtered![0]
// theMeaningLabel.text = dic["kurdi"]
}
else {
// theMeaningLabel.text = "Yay!‌"
}
getting
Binary operator '>' cannot be applied to operands of type 'Int?' and 'Int'
A literal translation from Objective-C to Swift is not recommended because in many cases it does not take advantage of the improved semantics of Swift.
As Apple removed all path manipulating API from String and added more URL related API to other classes use always URL if possible
let url = Bundle.main.url(forResource: "list", withExtension: "plist")!
The forced unwrapped url is safe since the file is required to be in the bundle. A crash will reveal a design error and must not happen at runtime.
Unlike the Foundation collection types the Swift collection types don't support the implicit property list conversion so the Swift way is to use Data and PropertyListSerialization
let data = try! Data(contentsOf: url)
let dataArray = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [Any]
The forced try! is as safe as forced unwrapping the url for the same reasons.
[Any] is the most unspecified Swift Array. If the array is supposed to be an array of dictionaries write [[String:Any]] or even [[String: String]] if all values are String. If it's supposed to be an array of Strings write [String]. Swift encourages the developer to be as type specific as possible.
An NSPredicate can be replaced with the filter function in Swift. For example an array of strings can be filtered case insensitively with
let filtered = dataArray.filter { $0.compare(searchText, options:.caseInsensitive) == .orderedSame }
If the array contains dictionaries you need to specify the keys (same as NSPredicate)
let filtered = dataArray.filter { ($0["foo"] as! String).compare(searchText, options:.caseInsensitive) == .orderedSame }
Finally the ObjC expression
if (filtered.count > 0) { ...
can be written much more descriptive in Swift
if filtered.isEmpty == false { ...
or
if !filtered.isEmpty { ...
If you are looking for only one item you can even write
if let foundObject = dataArray.first(where: { $0.compare(searchText, options:.caseInsensitive) == .orderedSame }) { ...

Pointer to inner nodes of JSON (NSDictionary)

{
"outterList": {
"section1": {
"entry1": {
"value": ""
},
"entry2": {
"value": ""
},
"entry3": {
"value": ""
},
"innerSection": {
"entry1": {
"value": ""
},
"entry2": {
"value": ""
}
}
},
"section2": {
"entry1": {
"value": ""
}
}
}
}
Problem statement is to read the above json and store it back in the same format with the "value" fields updated as per the local logic.
I initially go and parse the above NSDictionary and convert it to an NSArray (Mutable), which holds all the "entry" nodes in a custom holder class.
Is it possible that my NSArray's holder object can store a direct pointer to "outterList" -> "section1" -> "innerSection" -> "entry2", so that whenever I get a new value from my logic and I have to update it, I can do that immediately on the fly and not parsing it again to reach to that node.
Value in a dictionary will be changed if you modify the object itself:
myObj.value = newValue;
but it won't change if you simply assign pointer to another object:
myObj = [MyObj new];
so it won't work with JSON strings or numbers.
Also you can access dictionary values faster using valueForKeyPath like so:
[dict setValue:newValue forKeyPath:#"outterList.section1.entry1.value"]

Resources