user165242
user165242

Reputation: 1399

Decode nested JSON with similar keys using Codable

I am trying to decode a nested JSON. The problem is that the top level and a nested keys' names are similar. Like:

{
    success: bool
     message: String
     error: {
      message: String
         }
} 

From the back end I would be receiving a success message or a failed message. If the success is true, the error key would not be returned back and if it is false, then the error along with the message is sent.

so if it is successful:

{
    success: true
     message: "Success message"
} 

If it fails:

    {
        success: false
         error:{
              message: "Failed message"
        }
    } 

The above will be the returned json. This is my struct for decoding:

struct loginResponse : Codable{
    var success: Bool
    var success_message: String
    var error_message: String


enum loginResponseKeys: String, CodingKey{
    case success
    case error
    case success_message = "message" // raw value is not unique
    case error_message = "message"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: loginResponseKeys.self)
    let error = try container.nestedContainer(keyedBy: loginResponseKeys.self, forKey: .error)
    error_message = try error.decode(String.self, forKey: .error_message)
    let message = try container.decode(String.self, forKey:.success_message)
}

Rightly so, it says that the raw value is not unique. But how do I overcome that?

Upvotes: 2

Views: 652

Answers (3)

decybel
decybel

Reputation: 61

Using KeyedCodable it may be, notice you will have single "message" property that will contain successful or failed message depending on "success" value:

struct LoginResponse: Codable, Keyedable {
    private(set) var success: Bool!
    private(set) var message: String!

    mutating func map(map: KeyMap) throws {
        try success <-> map["success"]
        let messageKey = success ? "message" : "error.message"
        try message <-> map[messageKey]
    }

    init(from decoder: Decoder) throws {
        try KeyedDecoder(with: decoder).decode(to: &self)
    }
}

Upvotes: -1

Abdelahad Darwish
Abdelahad Darwish

Reputation: 6067

you can create struct for ErrorMessage

struct LoginResponse: Codable {
    let success: Bool
    let message: String?
    let error: ErrorMessage?
}

struct ErrorMessage: Codable {
    let message: String?
}


extension LoginResponse {
    init(data: Data) throws {
        self = try JSONDecoder().decode(LoginResponse.self, from: data)
    }
}

Assume that this Json:

{
    "success": true,
     "message": "success",
     "error": {
      "message": "Error Message"
         }
} 

Upvotes: 4

David Pasztor
David Pasztor

Reputation: 54706

You just need to create a nested ErrorResponse struct. Make both message and error optional and only decode one of them depending on the value of success.

You should also conform to the Swift naming convention, which is UpperCamelCase for type names and lowerCamelCase for variable names.

struct LoginResponse : Codable{
    let success: Bool
    var successMessage: String?
    var error: ErrorResponse?

    struct ErrorResponse: Codable {
        let message: String
    }


    enum LoginResponseKeys: String, CodingKey{
        case success, error, successMessage = "message"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: LoginResponseKeys.self)
        success = try container.decode(Bool.self, forKey: .success)
        if success {
            successMessage = try container.decode(String.self, forKey: .successMessage)
        } else {
            error = try container.decode(ErrorResponse.self, forKey: .error)
        }
    }
}

Upvotes: 3

Related Questions