Say I have the following code:
import Foundation
let jsonData = """
[
{"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
{"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
]
""".data(using: .utf8)!
struct Person: Codable {
let firstName, lastName: String
let age: String?
enum CodingKeys : String, CodingKey {
case firstName = "firstname"
case lastName = "lastname"
case age
}
}
let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
print(decoded)
Everything is working except age
is always nil
. Which makes sense. My question is how can I set the Person's age = realage
or 28
in the first example, and nil
in the second example. Instead of age
being nil
in both cases I want it to be 28
in the first case.
Is there a way to achieve this only using CodingKeys
and not having to add another struct or class? If not how can I use another struct or class to achieve what I want in the simplest way possible?
Lots of great answers here. I have certain reasons for not wanting to make it into it's own data model. Specially in my case it comes with a lot of data I don't need and this specific thing I need corresponds more to a person than an age model.
I'm sure others will find this post useful tho which is amazing. Just to add to that I will post my solution for how I decided to do this.
After looking at the Encoding and Decoding Custom Types Apple Documentation, I found it was possible to build a custom decoder and encoder to achieve this (Encode and Decode Manually).
The one change that is included in the code above that Apple doesn't mention is the fact that you can't use extensions like in their documentation example. So you have to embed it right within the struct or class.
Hopefully this helps someone, along with the other amazing answers here.
There are times to trick the API to get the interface you want.
Here the API (
firstName
,lastName
,age
) is kept and the JSON is preserved in both directions.My favorite approach when it comes to decoding nested JSON data is to define a "raw" model that stays very close to the JSON, even using
snake_case
if needed. It help bringing JSON data into Swift really quickly, then you can use Swift to do the manipulations you need:Also, I recommend you to be judicious with the use of
Codable
, as it implies bothEncodable
andDecodable
. It seems like you only needDecodable
so conform your model to that protocol only.For greater flexibility and robustness, you could implement an
Age
enumeration to fully support your data model head-on ;) For instance:and then use it in your
Person
type:Decode as before:
prints:
Finally, the new
realAge
computed property provides the behavior you were after initially (i.e., non-nil only for real ages):You can use like this :
You can call like this :