Search code examples
jsonswiftstructcodabledecodable

Is it possible to decode single level JSON into 2 separate models?


I have a JSON response that contains information about a user.

{
  "userId": "123456789",
  "email": "\"some.email@some.domain.tld",
  "firstName": "\"foo\"",
  "lastName": "\"bar\"",
  "name": "\"foo bar",
  "bio": "\"boo baz\"",
  "age": "42"
}

I'd like to create 2 models, User and Profile from the same JSON, with a single request.

I'd then like Profile to be a property on the User struct.

At the moment, my standard struct looks like this -

struct User: Codable, Equatable {
    var userId: String
    var email: String
    var firstName: String
    var lastName: String
    var name: String?
    var bio: String?
    var age: Int?
}

I would prefer however to do something like this -

struct User: Codable, Equatable {
    var userId: String
    var email: String
    var profile: UserProfile // I'd like to wrap the profile info up in this field
}

struct UserProfile: Codable, Equatable {
    var firstName: String
    var lastName: String
    var name: String?
    var bio: String?
    var age: Int?
}

As profile does not exist on the response, I do not understand how I can decode the rest of the response into it.


Solution

  • Firstly, you should consider making all of the fields on your model immutable.

    It is good practice not to change your data, but create an updated copy.

    However, this should work -

    let json = Data("""
     {
       "userId": "123456789",
       "email": "some.email@some.domain.tld",
       "firstName": "foo",
       "lastName": "bar",
       "name": "foo bar",
       "bio": "boo baz",
       "age": 42
     }
    """.utf8)
    
    struct User: Codable, Equatable {
        let userId: String
        let email: String
        let profile: UserProfile
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            userId = try values.decode(String.self, forKey: .userId)
            email = try values.decode(String.self, forKey: .email)
            profile = try UserProfile(from: decoder)
        }
    
    }
    
    struct UserProfile: Codable, Equatable {
            let firstName: String
            let lastName: String
            let name: String?
            let bio: String?
            let age: Int?
    }
    
    var result = try! JSONDecoder().decode(User.self, from: json)
    print(result.profile)