简体   繁体   中英

Decode custom JSON with Decodable + Realm Swift

From the server I have a big JSON returned that looks something like this:

{
  "id": "123",
  "status": "ok",
  "person": {
    "administration": {
      "name": "John"
    }
  },
  "company": {
    "name": "Test"
  }
}

I have a struct:

struct Info: Decodable, Object {
  let id: String
  let status: String
  let personName: String
  let companyName: String
}

It conforms to Decodable protocol and also is a Object (Realm entity). My question is: Am I able somehow to decode the name of the person in personName? Something like person.administration.name.
I want the end Realm Object, to be a flat one and mostly all of the fields are strings.
Should I create separate structs for Person/Company without being Realm Objects and in decode method to set the corresponding value to "personName"?

let personName: String = try container.decode((Person.Administration.name).self, forKey: .personName)

You can simply use containers to decode nested data with Decodable , ie

struct Info: Decodable {
    let id: String
    let status: String
    let personName: String
    let companyName: String

    enum CodingKeys: String, CodingKey {
        case id, status
        case person, administration
        case company
        case name
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(String.self, forKey: .id)
        status = try values.decode(String.self, forKey: .status)

        //Decoding personName
        let person = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .person)
        let administration = try person.nestedContainer(keyedBy: CodingKeys.self, forKey: .administration)
        personName = try administration.decode(String.self, forKey: .name)

        //Decoding companyName
        let company = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .company)
        companyName = try company.decode(String.self, forKey: .name)
    }
}

Example:

I've decoded the JSON you provided above, ie

if let data = json.data(using: .utf8) {
    let info = try? JSONDecoder().decode(Info.self, from: data)
    print(info)
}

The output it gives is:

(id: "123", status: "ok", personName: "John", companyName: "Test")

You can separate out the CodingKeys for all the different levels as per your wish. I kept them at the same level for simplicity.

Suggestion: Try using the optional types with Codable . This is because the API response can be unexpected. And if you don't get any expected key-value pair, you might end up getting a nil while creating the object.

It is best practice to separate transport types you're parsing your JSON into and types to represent object in the storage.

But if you want to use this combined types you should do something like this:

struct Info: Decodable {
    let id: String
    let status: String
    let personName: String
    let companyName: String

    // JSON root keys
    private enum RootKeys: String, CodingKey {
        case id, status, person, company
    }
    // Keys for "person" nested "object"
    private enum PersonKeys: String, CodingKey {
        case administration
    }
    // Keys for "administration" and "company"
    private enum NamedKeys: String, CodingKey {
        case name
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: RootKeys.self)

        self.id = try container.decode(String.self, forKey: .id)
        self.status = try container.decode(String.self, forKey: .status)

        let personContainer = try container.nestedContainer(keyedBy: PersonKeys.self, forKey: .person)
        let administrationContainer = try personContainer.nestedContainer(keyedBy: NamedKeys.self, forKey: .administration)
        self.personName = try administrationContainer.decode(String.self, forKey: .name)

        let companyContainer = try container.nestedContainer(keyedBy: NamedKeys.self, forKey: .company)
        self.companyName = try companyContainer.decode(String.self, forKey: .name)
    }
}

I separated keys into three different CodingKey types for some type safety, and to prevent accidental mixup.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM