This question already has an answer here:
How can I decode a JSON response with an unknown key in Swift?
(1 answer)
Closed 3 years ago.
I want to decode the following object from the server
{"USD":6385.74,"JPY":715249.73,"EUR":5582.36}
but I want to use a decodable struct with unknown key and value.Is this possible?
Regards,
Spyros
You can try
let res = try? JSONDecoder().decode([String:Double].self,from:data)
print(res["USD"])
which will enable you to decode any key
When I work with JSONs that are not fully known (as in I know all the possible keys and need all of them) I used SwiftyJSON library: https://github.com/SwiftyJSON/SwiftyJSON
It's much easier to work with than the built in JSON decoder
in your case it would be:
var jsonString = "{\"USD\":6385.74,\"JPY\":715249.73,\"EUR\":5582.36}"
let json = JSON(parseJSON: jsonString)
then you can do a bunch of stuff like iterating over keys
for (key, value) in json {
if let currency = key.string {
print (currency,value)
}
}
Check out the documentation in https://github.com/SwiftyJSON/SwiftyJSON
Related
First off, what do we call a dictionary with a format like this in iOS?
(
{
name = "Apple";
value = "fruit-1";
},
{
name = "Banana";
value = "fruit-2";
}
)
And for my main question. I somehow need to format a string of JSON, like this:
[{"name":"Apple","value":"fruit-1"},{"name":"Banana","value":"fruit-2"}]
into whatever that format is called (of the string above).
For context, the existing approach of my project uses CoreData where the Server response (which uses the mystery format above) gets saved locally as a String, and I want to follow that format.
EDIT: for more context, I really need to just get the first format into the database because a module of a project was built to read the data with that format (e.g. make use of NSString.propertyList()).
Using a library called ios hierarchy viewer, I can see the saved object in the device.
Original format, server json to db (core data) in Objective-C:
What I've been trying to do in Swift, server json to local using JSONSerialization:
First off, what do we call a dictionary with a format like this in iOS?
According to the documentation of NSString.propertyList(), that's a "text representation of a property list".
It's a wonky, non-standard pretty-printing obtained by calling NSArray.description or NSDictionary.description.
Here's an example that shows a round-trip of data:
// The opening `{` indentation is fucky, but that's how it's generated.
let inputPropertyList = """
(
{
name = "Apple";
value = "fruit-1";
},
{
name = "Banana";
value = "fruit-2";
}
)
"""
// The result is an `Any` because we don't know if the root structure
// of the property list is an array or a dictionary
let deserialized: Any = inputPropertyList.propertyList()
// If you want the description in the same format, you need to cast to
// Foundation.NSArray or Foundation.NSDictionary.
// Swift.Array and Swift.Dictionary have a different description format.
let nsDict = deserialized as! NSArray
let roundTrippedPropertyList = nsDict.description
print(roundTrippedPropertyList)
assert(roundTrippedPropertyList == inputPropertyList)
The second format you show is what you get when you display an object in the debug console. That's the output of the object's description property. It isn't a "JSON string", exactly.
If you want to convert your objets to a true JSON string, see below.
As Alexander pointed out, the first string in your question is the output from NSString's propertyList() function. The format looks quite similar to "pretty-printed" JSON, but it's different enough that it it won't work that way.
The `propertyList() function is a debugging-only function, and I don't know of an existing way to parse that back into objects. If that is the string that's being sent by your server, your server is broken. If that's what you see in core data when you log the contents of a field, it's probably a misunderstanding on your part.
To convert an object to pretty JSON, see this answer, where I created an extension to the Encodable format that implements a property "prettyJSON":
extension Encodable {
var prettyJSON: String {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let data = try? encoder.encode(self),
let output = String(data: data, encoding: .utf8)
else { return "Error converting \(self) to JSON string" }
return output
}
}
That should work for any object that supports the Encodable protocol. (And your object should.)
Here is the general structure of a team from our JSON file:
{"Team11":{
"results":{
"leg1":[
{"g":"m","n":"Name1"}
],"leg2":[
{"g":"m","n":"Name2"}
],"leg3":[
{"g":"m","n":"Name3"}
],"leg4":[
{"g":"m","n":"Name4"}
],"leg5":[
{"g":"m","n":"Name5"}
],"leg6":[
{"g":"m","n":"Name6"}
],"leg7":[
{"g":"m","n":"Name7"},{"g":"m","n":"Name8"}
]
},"tn":"TeamName",
"division":"co"
}
}
So far we are able to parse up into results categories leg1, leg2, etc. Accessing the info contained in the bracket arrays has not worked so far.
My current idea on why it is failing is because we are storing the JSON teams incorrectly via String:Any.
My other theory is I just haven't been able to find the correct documentation. Any pointers on where to look or tips would be huge!
Make sure to add What hasn't worked for you and what you have tried? As a New contributor you need to learn how to post questions. Give it a try with my below answer.
Use Codable to parse the JSON like below,
let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
// Welcome
struct Welcome: Codable {
let team11: Team11
enum CodingKeys: String, CodingKey {
case team11 = "Team11"
}
}
// Team11
struct Team11: Codable {
let results: [String: [Result]]
let tn, division: String
}
// Result
struct Result: Codable {
let g, n: String
}
Note: Your JSON is missing open and ending curly brackets, and I've updated that in your question.
Are you trying to parse the JSON manually? I'm not sure where you're at with your code, but the standard way to parse a JSON string into an object is this:
let jsonData = myJSONString.data(using: .utf8)
There is an issue with your JSON though. You can validate a JSON file on this link.
I'm trying to access the data downloaded from a website which looks like this:
{"quizzes":[
{"id":1, "question": "Can't solve this :("},
{"id":2, "question": "Someone help pls"}]}
The data downloaded looks way more complex, with more values, more keys, and some keys being associated to another dictionary of Key:String, but since I can't even access the most simple fields I thought I would start with this.
I'm using JSONSerialization, so my question is:
If I want to create a variable where i can save the downloaded data... Which would be it's type? I would say [String:[String:Any]], but I'm not sure if "quizzes" represents a key on this specific key, since the data starts with '{' and not with '['.
Instead of using JSONSerialization one could use JSONDecoder.
Example
struct Question: Decodable {
let id: Int
let question: String
}
struct Quiz: Decodable {
let quizzes: [Question]
}
Assuming jsonStr is a string with the JSON in your question:
if let jsonData = jsonStr.data(using: .utf8) {
if let quiz = try? JSONDecoder().decode(Quiz.self, from: jsonData) {
for question in quiz.quizzes {
print("\(question.id) \"\(question.question)\"")
}
}
}
Output
1 "Can't solve this :("
2 "Someone help pls"
This question already has answers here:
Swift JSONDecode decoding arrays fails if single element decoding fails
(16 answers)
Closed 5 years ago.
Is it possible to return the successfully decoded objects of an array if one of the objects throws a DecodingError? I'm communicating with quite a ropey API which intermittently sends me some malformed JSON which causes a DecodingError to be thrown and the entire array of objects (including valid entries) is not parsed.
I don't know if there's some way to do it manually, but given an array of JSON with some duff data in it, I'd like to somehow still get at the objects which can be successfully decoded.
EDIT: As requested, code sample:
let json = """
[
{"prop1": 1, "prop2": 2, "prop3": 3},
{"prop1": 1, "prop2": 2, "prop3": 3},
{"prop1": 1, "prop2": 2}
]
""".data(using: .utf8, allowLossyConversion: false)!
struct MyStruct: Codable {
let prop1: Int
let prop2: Int
let prop3: Int
}
// Throws keyNotFound (uncaught obviously) and returns nil because prop3 in 3rd object is missing
let decoded = try? JSONDecoder().decode(MyStruct.self, from: json)
Obviously a contrived example, but hopefully illustrates my point. If a mandatory property is missing from an object in the response array then I'd like that object to be removed from the list of decoded objects rather than bombing out and returning nothing.
This also applies to all the other DecodingErrors (dataCorrupted, typeMismatch, valueNotFound).
How about decoding item per item with error Handling embbeded ?
This way your program would continue, plus you could display a decoding error for a given item.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html
You can custom decoding.
JSONDecoder().dataDecodingStrategy = .custom({ (decoder) -> Data in
// custom decode
return Data()
})
Or handle with unexpected float.
JSONDecoder().nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "s1", negativeInfinity: "s2", nan: "s3")
Let's say there's a JSON-string I got from server: "\"3\"" (with quotes, i.e. length == 3 here)
In Android-world, I can do:
gson.fromJson(json, new TypeToken<String>() {}.getType()); - it returns "3" (i.e. length == 1)
In C#-world, can use NewtonSoft.Json:
JsonConvert.DeserializeObject<string>(json, settings) - it returns "3" (i.e. length == 1)
And other way around, I do have string I want to serialize as a JSON.
In Android I'd do gson.toJson("\"3\"") and in C# - JsonConvert.SerializeObject("\"3\"")
The problem with JSONSerialization is that it doesn't treat plain string as a valid JSON: JSONSerialization.isValidJSONObject("\"3\"") == *false*
What would be equivalent in Swift / Obj-C world?
The ugly workaround I've found (except of just adding/removing quotes) so far is to wrap string into 1-item-array to make JSONSerialization happy and then remove "[","]" from resulted JSON-string (and other way around - add "[", "]" before deserialization), but it's a way too disgusting to be the real solution for this problem.
When de-serializing JSON which does not have an array or
dictionary as top-level object you can pass the
.allowFragments option:
let jsonString = "\"3\""
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
if let str = json as? String {
print(str) // 3
}
However, there seems to be no way to serialize a plain string to JSON
with the JSONSerialization class from the Foundation library.
Note that according to the JSON specification,
a JSON object is a collection of name/value pairs (dictionary) or
an ordered list of values (array). A single string is not a valid
JSON object.