I have this pretty basic and straight forward mapping of an object into dictionary. I am using and parsing dictionary on the top level. One of its fields is an array of other dictionaries. To set them I use flatMap
which seems an appropriate method but since the object inside is not nullable this method suddenly returns tuples (at least it seems that way) instead of dictionaries.
I created a minimum example that can be pasted into a new project pretty much anywhere any of your execution occurs which should give better detail then any description:
func testFlatMap() -> Data? {
class MyObject {
let a: String
let b: Int
init(a: String, b: Int) { self.a = a; self.b = b }
func dictionary() -> [String: Any] {
var dictionary: [String: Any] = [String: Any]()
dictionary["a"] = a
dictionary["b"] = b
return dictionary
}
}
let objects: [MyObject] = [
MyObject(a: "first", b: 1),
MyObject(a: "second", b: 2),
MyObject(a: "third", b: 3)
]
var dictionary: [String: Any] = [String: Any]()
dictionary["objects"] = objects.flatMap { $0.dictionary() }
dictionary["objects2"] = objects.map { $0.dictionary() }
print("Type of first array: " + String(describing: type(of: dictionary["objects"]!)))
print("Type of first element: " + String(describing: type(of: (dictionary["objects"] as! [Any]).first!)))
print("Type of second array: " + String(describing: type(of: dictionary["objects2"]!)))
print("Type of second element: " + String(describing: type(of: (dictionary["objects2"] as! [Any]).first!)))
return try? JSONSerialization.data(withJSONObject: dictionary, options: [])
}
_ = testFlatMap()
So this code crashes saying
'NSInvalidArgumentException', reason: 'Invalid type in JSON write (_SwiftValue)'
(Using do-catch makes no difference which is the first WTH but let's leave that for now)
So let's look at what the log said:
Type of first array: Array<(key: String, value: Any)>
Type of first element: (key: String, value: Any)
Type of second array: Array<Dictionary<String, Any>>
Type of second element: Dictionary<String, Any>
The second one is what we expect but the first just has tuples in there. Is this natural, intentional?
Before you go all out on "why use flatMap for non-optional values" let me explain that func dictionary() -> [String: Any]
used to be func dictionary() -> [String: Any]?
because it skipped items that were missing some data.
So only adding that little ?
at the end of that method will change the output to:
Type of first array: Array<Dictionary<String, Any>>
Type of first element: Dictionary<String, Any>
Type of second array: Array<Optional<Dictionary<String, Any>>>
Type of second element: Optional<Dictionary<String, Any>>
which means the first solution is the correct one. And on change there are no warnings, no nothing. The app will suddenly just start crashing.
The question at the end is obviously "How to avoid this?" without too much work. Using dictionary["objects"] = objects.flatMap { $0.dictionary() } as [[String: Any]]
seems to do the trick to preserve one-liner. But using this seems very silly.