Parsing fields in Codable structs that can be of any JSON type
Swift 4 brought a lot of cool things like the Codable protocol. It made parsing JSON easier and painless. But some things that were simple with the previous API are not so trivial with the new Codable protocol.
I am working with a JSON schema definition that has a field that can be any JSON type
With the old JSONSerialization API, you can declare a property of type
Any in a struct, and assign it’s value by retrieving it from the JSON Dictionary.
With Codable it’s not that simple. If you declare the property as
Any like this:
You will get the following error:
cannot automatically synthesize 'Decodable' because 'Any' does not conform to 'Decodable'
A simple way to solve the issue is to implement custom parsing:
First the initializer tries to parse the encoded data as String. If it fails, it tries to parse it as an Int.
This solution works. You get the correct result when you try to parse
Decoding JSON objects is not as simple. Since you do not know the type, your only option is to try to decode it as
[String: Any]. But
[String: Any] is not Decodable because
Any is not Decodable. You can iterate over the key and values and apply the same string, int and object checking, and it will never stop.
The first thing you need to do is to define a JSON structure:
And make it Decodable:
Thanks to JSONValue now being Decodable,
[String: JSONValue] and
[JSONValue] work out of the box. Your Struct does not need custom parsing anymore.
JSONValue is a pretty useful Enum. No need to cast an
Any property anymore. You can switch it to unwrap the associated value.
This looks better, but it doesn’t work. It makes your MacBook fans to spin like crazy. The compiler doesn’t like the nil coalescing operator. A trick that I learned to solve this is to create an extension to Optional that does the same:
Now you can replace the operators with the
And finally you can add one more sugar spoon to our solution with another Optional extension.
Which will make our solution almost one line long (a pretty big line though 😅).
Now you have an elegant Struct that can be used to parse any kind of JSON property.
You can even take it to the extreme and parse the whole JSON document. Actually, you can parse any valid JSON document now:
It’s like parsing a
[String: Any] with JSONSerialization but a little bit more type safe.
This JSONValue solved my problem, and it may solve yours too. Let me know what you think of it. In future posts I will improve it by making it Encodable as well, which will provide us a DSL like way to write JSON.