0

I have a (annoying) situation where my back-end returns an object like this:

{
"user": {
        "name": [
            "John"
        ],
        "familyName": [
            "Johnson"
        ]
    }
}

where each property is an array that holds a string as its first element. In my data model struct I could declare each property as an array but that really would be ugly. I would like to have my model as such:

struct User: Codable {
    var user: String
    var familyName: String
}

But this of course would fail the encoding/decoding as the types don't match. Until now I've used ObjectMapper library which provided a Map object and currentValue property, with that I could declare my properties as String type and in my model init method assig each value through this function:

extension Map {
    public func firstFromArray<T>(key: String) -> T? {
        if let array = self[key].currentValue as? [T] {
            return array.first
        }
        return self[key].currentValue as? T
    }
}

But now that I am converting to Codable approach, I don't know how to do such mapping. Any ideas?

2 Answers 2

2

You can override init(from decoder: Decoder):

let json = """
{
    "user": {
        "name": [
        "John"
        ],
        "familyName": [
        "Johnson"
        ]
    }
}
"""

struct User: Codable {
    var name: String
    var familyName: String

    init(from decoder: Decoder) throws {
        let container:KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
        let nameArray = try container.decode([String].self, forKey: .name)
        let familyNameArray = try container.decode([String].self, forKey: .familyName)
        self.name = nameArray.first!
        self.familyName = familyNameArray.first!
    }

    enum CodingKeys: String, CodingKey {
        case name
        case familyName
    }
}

let data = json.data(using: .utf8)!
let decodedDictionary = try JSONDecoder().decode(Dictionary<String, User>.self, from: data)
print(decodedDictionary) // ["user": __lldb_expr_48.User(name: "John", familyName: "Johnson")]

let encodedData = try JSONEncoder().encode(decodedDictionary["user"]!)
let encodedStr = String(data: encodedData, encoding: .utf8)
print(encodedStr!) // {"name":"John","familyName":"Johnson"}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for a good answer. I decided to go with the computed property way though.
1

My tendency would be to adapt your model to the data coming in and create computed properties for use in the application, e.g.

struct User: Codable {
    var user: [String]
    var familyName: [String]

    var userFirstName: String? {
        return user.first
    }

    var userFamilyName: String? {
        return familyName.first
    }
}

This allows you to easily maintain parody with the data structure coming in without the maintenance cost of overriding the coding/decoding.

If it goes well with your design, you could also have a UI wrapper Type or ViewModel to more clearly differentiate the underlying Model from it's display.

1 Comment

Thanks. I like this one better than overriding init.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.