JSONDecoder changing order of elements [duplicate] - json

I am trying to create a dictionary that I can make into a JSON formatted object and send to the server.
Example:
var users = [
[
"First": "Albert",
"Last": "Einstein",
"Address":[
"Street": "112 Mercer Street",
"City": "Princeton"]
],
[
"First": "Marie",
"Last": "Curie",
"Address":[
"Street": "108 boulevard Kellermann",
"City": "Paris"]]
]
I use this function
func nsobjectToJSON(swiftObject: NSObject) -> NSString {
var jsonCreationError: NSError?
let jsonData: NSData = NSJSONSerialization.dataWithJSONObject(swiftObject, options: NSJSONWritingOptions.PrettyPrinted, error: &jsonCreationError)!
var strJSON = NSString()
if jsonCreationError != nil {
println("Errors: \(jsonCreationError)")
}
else {
// everything is fine and we have our json stored as an NSData object. We can convert into NSString
strJSON = NSString(data: jsonData, encoding: NSUTF8StringEncoding)!
println("\(strJSON)")
}
return strJSON
}
But my result is this:
[
{
"First" : "Albert",
"Address" : {
"Street" : "112 Mercer Street",
"City" : "Princeton"
},
"Last" : "Einstein"
},
{
"First" : "Marie",
"Address" : {
"Street" : "108 boulevard Kellermann",
"City" : "Paris"
},
"Last" : "Curie"
}
]
Problem: why is the last name last? I think it should be above address. Please let me know what I am doing wrong with the NSDictionary for this to come out wrong. Any help would be very much appreciated - thank you.

To post what has already been said in comments: Dictionaries are "unordered collections". They do not have any order at all to their key/value pairs. Period.
If you want an ordered collection, use something other than a dictionary. (an array of single-item dictionaries is one way to do it.) You can also write code that loads a dictionary's keys into a mutable array, sorts the array, then uses the sorted array of keys to fetch key/value pairs in the desired order.
You could also create your own collection type that uses strings as indexes and keeps the items in sorted order. Swift makes that straightforward, although it would be computationally expensive.

I did like this.
let stagesDict = NSDictionary()
if let strVal = sleepItemDict["stages"] as? NSDictionary {
stagesDict = strVal
let sortedKeys = (stagesDict.allKeys as! [String]).sorted(by: <)
var sortedValues : [Int] = []
for key in sortedKeys {
let value = stagesDict[key]!
print("\(key): \(value)")
sortedValues.append(value as! Int)
}
}

Related

Decode JSON with different structures with Swift

I have json data with the same core structure from a nosql database (PK, SK, attributes). The attributes part will be different depending on the value of SK.
Example:
[
{
"PK": "POLL#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"SK": "#METADATA#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"attributes": {
"latitude": "53.34589121858683",
"longitude": "-6.272215191675388",
"max_choices": 50,
"number": "1544693",
"poll_open": false,
}
},
{
"PK": "POLL#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"SK": "CHOICE#00a6ec5c-acc1-40f1-a087-31160d2cfc65",
"attributes": {
"distance": 790.95097525,
"latitude": 53.3416,
"price": "€€",
"categories": [
{
"title": "Ramen",
"alias": "ramen"
}
],
"vote_count": 0,
"longitude": -6.26274
}
}
]
Is it possible to use decode without errors? I've been stuck on this for hours.
I've defined the following:
struct Result: Codable {
var PK: String
var SK: String
var attributes: String
}
But, when I decode, I get the error:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "attributes", intValue: nil)], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))
I just want to decode 'attributes' as a generic string and parse it later depending on the value of SK when I know how to properly handle it. Why is this so difficult?
Do you need attributes right now? Or are you just looking for pk and sk? If you do not need it just do not include
var attributes: String
in your struct. It will not have a decode error and it will decode the other two, you just will not get the attributes parameter. It is not able to decode the attributes
as a string because it is not. It is really more like a dictionary. Swift does not know how to handle that unless you specifically tell it. That being said you could always do this
struct Result: Codable {
var PK: String
var SK: String
var attributes: Attributes
}
struct Attributes: Codable {
var lattitude: String
var longitude: String
//and whatever else you know you have
}
the key is only adding the values you know will be included in the attributes or else it will give an error
You need to handle this using JSONSerialization instead of Codable. Given you still want to use the same struct you need to change it to
struct Result {
var PK: String
var SK: String
var attributes: [String: Any]
}
and decode the json like this
var result: Result?
do {
if let dictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let pk = dictionary["PK"] as? String,
let sk = dictionary["SK"] as? String,
let attributes = dictionary["attributes"] as? [String: Any] {
result = Result(PK: pk, SK: sk, attributes: attributes)
}
} catch {
print(error)
}
Now you still need to convert the attributes ([String: Any]) property of Result to something more usable but that is beyond this question

Swift : How to create a dictionary dynamically from a json?

I'd like some advice from you. I would like to create a dictionary from a dynamic response fetch from an API and send that dictionary in an Alamofire POST request.
From what I have so far it's working but I'm not satisfied with what i've made and I think the code is really messy.
Here is an example of what I can receive
"content": {
"type": "form",
"fields": [
{
"type": "select",
"label": "Do you have your documents?",
"field": "user.has_docs",
"default": 0,
"choices": [
{
"value": 0,
"name": "Not yet"
},
{
"value": 1,
"name": "I do"
}
]
},
{
"type": "input",
"field": "user.date",
"label": "When do you arrive?",
}
]
}
After parsing the json with the Codable protocol, I have all my data in the Model Field
type: String
label: String
field: String
defaultValue: Int?
choice: [Choice]?
Choice
value: Int
name: String
So I want to create my dictionary and I want the following scheme :
{
"value": {
"user": {
"has_docs": 1,
"date": "29/07/2020"
}
}
}
The key named : "value" is always the same value, but the other one depends of the result from the API. the prefix of the field corresponding of "parent object" and the right part is the child.
Hard coding a dictionary in Swift is not that hard, I would do
let dict = [
"value": [
"user": [
"has_docs": 1,
"date": "29/07/2020"
]
]
]
But the troubles begin, at the attempt of creating a dictionary dynamically. Values inside user keep only the last one and replacing has_docs with date.
I have found a workaround with using flatmap and reduce but it only allows the type [String: String], unfortunately I need to write [String: Int] too in the dictionary.
here is a sample of the code
let flattenedDictionary = [key : dictionaries
.flatMap { $0 }
.reduce([String:String]()) { (dict, tuple) in
var nextDict = dict
nextDict.updateValue(tuple.1 as! String, forKey: tuple.0)
return nextDict
}]
parameters["value"] = flattenedDictionary
Here :
key = "user".
dictionaries = [["has_docs": 1], ["date": "29/07/2020"]]
Feel free to exchange if you need more informations
If you have any clue on how you could helping me, I'll highly appreciate, thanks for reading so far.
I hope I was very understandable.
Edit
From a general view : I'd like to create a dictionary dynamically
[String: [String: [String: Any]]]
A bit unclear if you have a [String: [String: [String: Any]]] or [String: [String: Any]] dictionary, but the concept of creating it dynamically would be rather similar.
var user: [String: Any] = [:]
user["has_docs"] = 1
user["date"] = Date()
let dict = ["value": user]

how to loop through an array of objects and create a json object?

I have an array of 300 objects which each object contain many parameters and I want to loop through the array to create a JSON object which contain only two parameters. my objects were created from this json structure in the array :
"publicationId": 13,
"name": null,
"code": "FIGA_20190516",
"parutionDate": "2019-05-16",
"quadri": "FIGA",
"ratio": null,
"numero": "0",
"createdAt": "2019-06-18 21:29:21",
"nbPages": 0,
"search": 1,
"down": 1,
"thumb_height": 0,
"quartPageWidth": 0,
"quartPageHeight": 0,
"pdfHeight": "0",
"pdfWidth": "0",
"PDF": "2019-06-18 21:29:21",
"XML": null,
"SendKM": "2019-06-18 21:29:21",
"KM": null,
"Melody": null,
"filename": null,
"SendNormalize": null,
"Normalized": null,
"librelioSubTitle": null,
"SendProcessArticle": null,
"ProcessedArticles": null,
"melodyCallBackUrl": null,
"htmlMilibris": false,
"htmlHarry": false,
"sendProcessLibrelio": null,
"processedLibrelio": null,
"id": 77066,
"_solrDocument": {},
"_repository": "FIGA/2019/05/FIGA_20190516/"
and I want to loop through the array to create a JSON object with this struct :
"availableParutions" : {
id : createdAt,
id : createdAt,
id : createdAt
}
I really appreciate your help guys !
You can create a new struct that represents the data you want in your Json output
struct NewStruct: Encodable {
let id: Int
let createAt: String
}
This struct conforms to Encodable so that it can be encoded to json but since it is so simple no extra coding is needed
Then you can convert your array of large objects to an array of NewStruct using map
let newArray = array.map { NewStruct(id: $0.id, createAt: $0.createdAt) }
If I understand it correctly this array should be a property named "availableParutions" so lets make an outer struct for the json
struct Output: Codable {
let availableParutions: [NewStruct]
}
and load it
let output = Output(availableParutions: newArray)
and encode it
do {
let jsonData = try JSONEncoder().encode(output)
} catch {
print(error)
}
You can try the following code:
Note: I have create json obj my self for evaluation purpose, you can use your json directly.
let json = [
[
"publicationId":11,
"createdAt":"2019-06-18 21:29:21",
"id":77063,
],
[
"publicationId":12,
"createdAt":"2019-06-18 21:29:21",
"id":77065,
],
[
"publicationId":13,
"createdAt":"2019-06-18 21:29:21",
"id":77066,
]
]
var jsonArr: [[String:Any]] = [[String:Any]]()
for obj in json {
let id = obj["id"] as! Int
let jsonObj = [
"\(id)": obj["createdAt"] as! String
]
jsonArr.append(jsonObj as [String : Any])
}
print(jsonArr)
You could create an empty dictionary, then iterate over json array and pull out id & createdAt. Update dictionary with id & createdAt. Finally set this dictionary as value of availableParutions in a new dictionary.
code:
var map: [Int: Any] = [:]
for item in json {
let id = item["id"] as! Int
map[id] = item["createdAt"]!
}
let outerObject = ["availableParutions": map]
print(outerObject)

Parsing JSON response in Swift 3

I've got an API endpoint that returns JSON in the following format:
[
{
"id": "1",
"name": "John"
},
{
"id": "2",
"name": "Jane"
},
{
"id": "3",
"name": "Nick"
}
]
I am trying to parse this in Swift 3, but I can only find examples to parse JSON formatted like so:
{
"blogs": [
{
"needspassword": true,
"id": 73,
"url": "http://remote.bloxus.com/",
"name": "Bloxus test"
},
{
"needspassword": false,
"id": 74,
"url": "http://flickrtest1.userland.com/",
"name": "Manila Test"
}
],
"stat": "ok"
}
, which has an extra level above what mine does.
So, where examples I've seen are simply parsing their data like jsonResponse["blogs"], I can't do that as my format is different.
How can I parse the format I've got, or how can I return a format that is easier to parse?
Any suggestions appreciated, thanks!
You can just do the following :
let data = // Data received from WS
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [[String : String]]
//json is now an array from dictionary matching your model
}
catch {
//handle error
}
This will parse it when placed in the network call.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as! [[String : AnyObject]]
let firstPerson = json[0]
print(firstPerson)
let id = firstPerson["id"] as! String
print(id)
let name = firstPerson["name"] as! String
print(name)
} catch {
//handle error
}
Also, I tend to be against advising third party libraries, but SwiftyJSON is an exception I make. If you want to try it, add this to your pod file:
pod SwiftyJSON', '3.0.0'
Documentation: https://github.com/SwiftyJSON/SwiftyJSON
EDIT - Answering Comment:
Replacement line:
if let id = firstPerson["id"] as? String {
print(id)
}
Replacement line (if you need to hold on to the value):
var thisId: String?
if let id = firstPerson["id"] as? String {
thisId = id
}
print(thisId ?? "")
i don't really know swift but there might be the equivalent of ajax json encoding (server side you json_encode your array and client side you json_decode the response)
the idea is to have the same formatter that encodes and decodes the data

Unwrapping Json Swift (found nil)

i currently trying to decode Json in Xcode, but i not succed to get one of them.
This is what i get :
[
{
"id": "1",
"title": "bmw",
"price": "500.00",
"description": "330",
"addedDate": "2015-05-18 00:00:00",
"user_id": "1",
"user_name": "CANOVAS",
"user_zipCode": "32767",
"category_id": "1",
"category_label": "VEHICULES",
"subcategory_id": "2",
"subcategory_label": "Motos",
"bdd": {}
}
"pictures":
[
{ "name": "http://cars.axlegeeks.com/sites/default/files/4315/media/images/2014_BMW_Z4_sDrive28i_3790993.jpg"
}
]
}
]
And i want to get the value of "name" for the "Pictures" lines but i have the error "unexpectedly found nil while unwrapping value".
For the others values i proceed this way :
let jsonData:NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options:NSJSONReadingOptions.MutableContainers , error: &error) as! NSDictionary
//Browse into JSON to get datas
let resp = jsonData["0"] as! NSDictionary
let user_nameR = resp["user_name"] as! String
let user_zipCodeR = resp["user_zipCode"] as! String
let titleR = resp["title"] as! String
let priceR = resp["price"] as! String
let descriptionR = resp["description"] as! String
Thank's for your help !
Pictures is in not in the subdictionary your other values are. This should work, but you should check all values if they are nil before you force cast them.
if let pictureArray = jsonData["pictures"] as? NSArray
{
if let pictureDict = pictureArray.firstObject as? NSDictionary
{
if let pictureName = pictureDict.objectForKey("name") as? String
{
NSLog("Picture name: %#", pictureName)
}
}
}
jsonData contains two sub-dictionaries, one without a key and one with key 'pictures'. Pictures is an array containing one sub-dictionary.