3

I receive the following 2 responses from different APIs

{
  "id": "jdu72bdj",
  "userInfo": {
    "name": "Sudhanshu",
    "age": 28,
    "country": "India"
  }
}

and

{
  "profileId": "jdu72bdj",
  "profileDetails": {
    "name": "Sudhanshu",
    "age": 28,
    "country": "India"
  }
}

This is in context with iOS development using Swift language. Basically the object structure is same but keys are different. I'm parsing these using Codable, but I cannot think of a way to parse using same struct. All I can think of is making 2 different structs like this -

public struct Container1: Codable {
  public let id: String
  public let userInfo: UserProfile?    
}

and

public struct Container2: Codable {
  public let profileId: String
  public let profileDetails: UserProfile?    
}

They both use common UserProfile struct.

public struct UserProfile: Codable {
  public let name: String?
  public let age: Int?
  public let country: String?
}

Is there a way to use one common container struct for both responses which parse response from 2 different keys. I do not want Container1 and Container2 since they both have same structure.

Any suggestions ?

2 Answers 2

5

One solution is to use a custom key decoding strategy using an implementation of CodingKey found in Apple's documentation. The idea is to map the keys of both of the json messages to the properties of the struct Container that will be used for both messages.

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keys in
    let key = keys.last!.stringValue
    switch key {
    case "id", "profileId":
        return AnyKey(stringValue: "id")!
    case "userInfo", "profileDetails":
        return AnyKey(stringValue: "details")!
    default:
        return keys.last!
    }        
})

where the custom implementation of CodingKey is

struct AnyKey: CodingKey {
    var stringValue: String
    var intValue: Int?

    init?(stringValue: String) {
        print(stringValue)
        self.stringValue = stringValue
        intValue = nil
    }

    init?(intValue: Int) {
        self.stringValue = String(intValue)
        self.intValue = intValue
    }
}

and then decode both json messages the same way using the below struct

struct Container: Codable {
    let id: String
    let details: UserProfile
}

let result = try decoder.decode(Container.self, from: data)
Sign up to request clarification or add additional context in comments.

7 Comments

You didn't mention how Container struct should be like. Apparently OP need to use only id and details as properties.
@gcharita thanks for pointing that out. I’ll update my answer.
Might need to add the explanation of your keyDecodingStrategy which is to treat id and profileId as it was the key id and userInfo and profileDetails as details. So when it hits your init(decoder:) you will have "replaced" the "real keys" with yours. I didn't know about it, happy to see that answer!
@Larme it’s required by the CodingKey protocol to handle both String and Int
@cheeseRoot Then I guess you will have to look for another solution unless you could rewrite that other module so it supports dependency injection. Either way, this is a new requirement that wasn’t mentioned in the original question.
|
1

You can use your own init from decoder

struct UniversalProfileContainer: Decodable {
    struct UserProfile: Decodable {
        var name: String
        var age: Int
        var country: String
    }
    
    enum CodingKeys: String, CodingKey {
        case id = "id"
        case profileId = "profileId"
        case userInfo = "userInfo"
        case profileDetails = "profileDetails"
    }

    let id: String
    let profile: UserProfile

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            id = try container.decode(String.self, forKey: .id)
        } catch {
            id = try container.decode(String.self, forKey: .profileId)
        }
        do {
            profile = try container.decode(UserProfile.self, forKey: .userInfo)
        } catch {
            profile = try container.decode(UserProfile.self, forKey: .profileDetails)
        }
    }
}

let first = """
{
  "id": "jdu72bdj",
  "userInfo": {
    "name": "Sudhanshu",
    "age": 28,
    "country": "India"
  }
}
"""
let second = """
{
  "profileId": "jdu72bdj",
  "profileDetails": {
    "name": "Sudhanshu",
    "age": 28,
    "country": "India"
  }
}
"""
let response1 = try? JSONDecoder().decode(UniversalProfileContainer.self,
                                            from: first.data(using: .utf8)!)

let response2 = try? JSONDecoder().decode(UniversalProfileContainer.self,
                                            from: second.data(using: .utf8)!)

Comments

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.