When I try to serialize json data in swift I get this error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'
Here is the code that is creating this issue:
request.httpBody = try? JSONSerialization.data(withJSONObject: data, options: [])
my data variable looks something like this:
let data = ["first_name": "John", "last_name": "Riverson", "post_info: userPost(title: "Some title", date_published: "some date")] as [String: Any]
I issue is caused due to the userPost struct, but I am not sure how to fix it.
It seems the result of userPost is not available for JSON.
According to JSONSerialization
A Foundation object that may be converted to JSON must have the following properties:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity
If you want to make userPost could be accpeted by JSONSerialization, refer to Using JSON with Custom Types, and apply Codable to it.
Your analysis is correct that it's due to the struct.
As per the documentation on JSONSerialization:
Foundation object that may be converted to JSON must have the
following properties:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
Ref:
https://developer.apple.com/documentation/foundation/jsonserialization
So you will need to convert the struct to dictionary to insert it here.
Something like:
let userPostDict: [String:Any] = //... your logic e.g. userPost.toDictionary
let data = ["first_name": "John",
"last_name" : "Riverson",
"post_info" : userPostDict]
You could have a toDictionary computed property inside your userPost struct that gives you a [String:Any]
Example:
extension YourStruct {
var toDictionary: [String:Any] {
//make the dictionary & return here
}
}
Related
I am trying to pass a Parameters [String:Any] value to JSONSerialization.data and it's throwing error every time.
I know the values of the [String:Any] dictionary is Swifty.JSON objects. But I am not able to convert them to NSDictionary object.
I populate the params from another dictionary like so:
var params = [String:Any]()
for (key, value) in self.myDictionary[self.selectedEntry] as! JSON {
print("\(key) - \(value.description)")
params[key]=value
}
And this is whats inside the params object after a print(params).
["searchOptions": {
"omit_saved_records" : false
}, "customFilters": {
"is_next_sellers_search" : "Y"
}, "use_code_std": [
"RSFR",
"RCON"
]]
I am passing the params to this function:
let json = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
This is where the error occur.
I am expecting this to simply work but I get this error instead:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'
What am I doing wrong here?
The JSON type of SwiftyJSON is a custom type and cannot be serialized.
Just get the dictionaryObject from the JSON object
if let params = self.myDictionary[self.selectedEntry].dictionaryObject {
And don't prettyPrint. The server doesn't care
let json = try JSONSerialization.data(withJSONObject: params)
Note: You are encouraged to drop SwiftyJSON in favor of Codable.
Get this error:
Fatal error: Unable to bridge NSNumber to Float. What is the problem?
This is the original message, it is float and not string.
{\"name\":\"Tomas\",\"gender\":\"male\",\"probability\":0.99,\"count\":594}
You have many different types of numbers in Swift/Foundation. Your NSKeyValueCoding has been set as instance of NSNumber (see excerpt of JSON serialization documentation below) so you need to read as is, and then ask to convert this NSNumber as Float if needed:
if let n = d.value(forKey: "probability") as? NSNumber {
let f = n.floatValue
}
JSONSerialization documentation says:
A Foundation object that may be converted to JSON must have the
following properties:
The top level object is an NSArray or
NSDictionary.
All objects are instances of NSString, NSNumber,
NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
You are using the wrong API.
Don’t use KVC (valueForKey) unless you really need KVC.
For getting a dictionary value use always key subscription (or objectForKey)
if let probability = d["probability"] as? Float {
print(probability)
}
I would recommend ditching the dictionary representation entirely and moving to a type-safe parser using Decodable:
struct User: Decodable {
let name: String
let gender: String
let probability: Float
let count: Int
}
let str = "{ \"name\": \"Tomas\", \"gender\": \"male\", \"probability\":0.99, \"count\": 594 }"
let data = str.data(using: .utf8)!
do {
let user = try JSONDecoder().decode(User.self, from: data)
} catch {
// handle errors
}
Some inside baseball here:
When swift is casting a floating point number from json to float, it has some kind of validation that makes sure the number lands on a terminating fractional value that the float data type can support.
E.g. if your value was 0.25 (1/4th), 0.875 (7/8ths), etc., it would not give you an error.
If your value does not fall on one of these terminating fractions (e.g. 0.33), it assumes some data could be lost to the lack of precision, and it throws an error.
If you don't care about the potential data loss, here is one way to convert the value to a float without much worry:
Float(jsonDictionary["jsonDictionaryKey"] as! Double)
I have two exactly json objects, one is created from a function, the other is hard-coded, the hardcoded one works, the other doesn't (it always complains about the error invalid top-level type , which is weird. Any tip? Tks
let myData = self.dailyMileage?.toDictionary()
let directData = ["orgId" : self.orgId, "driverId" : self.driverId, "date" : Utils.getTodaysDate() ] as [String : Any]
//this won't work unless I substitute myData with directData
let jsonData = try JSONSerialization.data(withJSONObject: myData, options: .prettyPrinted)
//this is the function that produces myData, and nothing is nil
public func toDictionary() -> [String : Any] {
let dict = [ "orgId" : orgId , "driverId": driverId, "date" : date] as [String : Any]
return dict
}
JSONSerialization as given in the documentation:
An object that may be converted to JSON must have the following properties:
The top level object is an NSArray or NSDictionary. All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString. Numbers are not NaN or infinity.
Other rules may apply. Calling isValidJSONObject(_:) or attempting a conversion are the definitive ways to tell if a given object can be converted to JSON data.
I think the one that's coming from the function might have an NSDate object instead of an NSString object.
The other reason is because your myData object is optional. JSONSerialization may give an error like that is the object is optional.
Please check if it is due to one of those two reasons. Feel free to suggest edits to make it better.
I am using JSONSerialization quite often in my project.
Here is an example of my JSONSerialization code:
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
Note: Options are missing for purpose and I normally have them in my project.
My problem is that I am not pretty sure what do these options: [] do?
What I have found about options:
NSJSONReadingMutableContainers:
Specifies that arrays and dictionaries are created as mutable objects.
NSJSONReadingMutableLeaves:
Specifies that leaf strings in the JSON object graph are created as
instances of NSMutableString.
NSJSONReadingAllowFragments:
Specifies that the parser should allow top-level objects that are not
an instance of NSArray or NSDictionary.
Note2: I found those definitions on : https://developer.apple.com/reference/foundation/nsjsonreadingoptions
My question is:
Can someone please explain me differences between those options , what should I use them for and if you could show me some code example of those options it would be perfect :).
Any help appreciated.
Thanks.
Short answer for the first two options:
Ignore them in Swift!
In Swift you can make objects mutable just with the var keyword.
In Objective-C on the other hand you need
NSJSONReadingMutableContainers to make the nested collection types mutable NSArray → NSMutableArray and NSDictionary → NSMutableDictionary.
NSJSONReadingMutableLeaves to make the value strings mutable → NSMutableString.
In both Objective-C and Swift if you are only reading the JSON you don't need mutability at all.
The third option NSJSONReadingAllowFragments is important if the root object of the received JSON is not an array and not a dictionary.
If it is an array or dictionary you can omit that option, too.
The pair of empty brackets [] represents No options (the options parameter can be omitted in Swift 3+).
You'd better know how JSON values are imported into iOS world:
JSON array -> NSArray
JSON object -> NSDictionary
JSON number -> NSNumber
JSON string -> NSString
JSON true -> NSNumber
JSON false -> NSNumber
JSON null -> NSNull
(You'd better also check the RFCs of JSON. RFC-4627, RFC-7159)
Then re-check the all options again:
mutableContainers (NSJSONReadingMutableContainers):
Guarantees the NSArrays or NSDictionarys contained in the result must be NSMutableArrays or NSMutableDictionarys. Someone says in older iOSs JSONSerialization (NSJSONSerialization) returned mutable objects without specifying mutableContainers, but depending on it is not recommended, and actually you can find someones reporting such code does not work in iOS 10.
In Swift, mutability is represented by var and let, so you have no need to use this option in Swifty codes. Only needed when you cast some parts of the deserialized result to NSMutableArray or NSMutableDictionary. I strongly recommend to rewrite such codes in a more Swifty manner.
mutableLeaves (NSJSONReadingMutableLeaves):
Guarantees the NSStrings contained in the result must be NSMutableStrings. Rarely used even in old Objective-C codes, ignore it.
allowFragments (NSJSONReadingAllowFragments):
In old RFC (RFC-4627), only array and object were valid as the outermost component of JSON. If you expect array or object (NSDictionary) from the server, NOT specifying this option would help you to find the invalid returned value from the server a little bit sooner.
Seeing the difference in codes:
Assume data1 is a valid UTF-8 representation of the following JSON:
[{"name": "aaa", "value": 123}, {"name": "bbb", "value": 456}]
And the code:
do {
let result = try JSONSerialization.jsonObject(with: data1)
let resultArray = result as! NSMutableArray //->This may cause your app crash
//->Could not cast value of type '__NSArrayI' (0x105e79c08) to 'NSMutableArray' (0x105e79cd0).
print(resultArray)
} catch {
print(error)
}
do {
let result = try JSONSerialization.jsonObject(with: data1, options: [.mutableContainers])
let resultArray = result as! NSMutableArray //->This should always work
print(resultArray) //->shows output...
} catch {
print(error)
}
And data2:
-1
And the comparison for it:
do {
let result = try JSONSerialization.jsonObject(with: data2)
print(result)
} catch {
print(error) //->Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
}
do {
let result = try JSONSerialization.jsonObject(with: data2, options: [.allowFragments])
print(result) //-> -1
} catch {
print(error)
}
Options: [] is an empty array returns nothing.
Whereas Options: [] can also be amend with:
NSJSONWritingOptions: for writing JSON data like.
NSJSONWritingOptions.NSJSONWritingPrettyPrinted: Specifies that the JSON data should be generated with whitespace designed to make the output more readable. If this option is not set, the most compact possible JSON representation is generated.
NSJSONReadingOptions: used when creating Foundation objects from JSON data.
NSJSONReadingOptions.MutableContainers: Specifies that arrays and dictionaries are created as mutable objects.
.mutableLeaves: Specifies that leaf strings in the JSON object graph are created as instances of NSMutableString.
.allowFragments: Specifies that the parser should allow top-level objects that are not an instance of NSArray or NSDictionary.
I have a json.I am trying to parse that with that code.But its says
Could not cast value of type '__NSArrayM' to 'NSDictionary'
do {
let dataDictionary: NSDictionary = try NSJSONSerialization.JSONObjectWithData(responseObject as! NSData, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary // <------ Error
if let customerArray = dataDictionary.valueForKey("cart") as? NSArray {
for js in customerArray {
let nameArray = js.valueForKey("name")
let idArray = js.valueForKey("id")
}
}
}
Thank you for your helps
The root object in your data is an array, not a object (dictionary).
You need to dynamically decide how to handle your JSON depending on the deserialized object.
What it's telling you is that the JSON object that you're parsing is not a dictionary, it's an array. So if you change it so that you treat its value as an array instead of a dictionary, you'll be able to iterate over that.
You need to reevaluate your JSON to ensure that it's structured the way you think it is. It would also be useful if you posted the JSON that you're trying to parse so that we can see it's structure as well.