How to use different keys in input and output when using Encodable protocol?

49 views Asked by At

Expected this: name -> fullname

struct Person: Codable {
    let name: String

    enum CodingKeys: String, CodingKey {
        case name = "fullname"
    }

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

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
    }
}

let jsonString = """
{
    "name": "John"
}
"""

let jsonDecoder = JSONDecoder()
if let jsonData = jsonString.data(using: .utf8),
   let decodedPerson = try? jsonDecoder.decode(Person.self, from: jsonData) {
    print(decodedPerson.name) // Output: "John"
}

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
if let encodedData = try? jsonEncoder.encode(decodedPerson),
   let encodedString = String(data: encodedData, encoding: .utf8) {
    print(encodedString)
}

But the reality that second output produce this:

{
  "name" : "John"
}
1

There are 1 answers

0
HangarRash On BEST ANSWER

If your goal is to decode the name key and output the fullname key (which is really confusing and means your code can't decode the JSON that it generates) then you need to update the CodingKeys to:

enum CodingKeys: String, CodingKey {
    case fullname = "fullname"
    case name = "name"
}

and you need to update the encode method to use .fullname instead of .name.

Then you can parse the original JSON with the name key and then generate new JSON with a fullname key.

Here's your code with some cleanup and the changes I suggest:

struct Person: Codable {
    let name: String

    enum CodingKeys: String, CodingKey {
        case fullname = "fullname"
        case name = "name"
    }

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

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .fullname) // Use fullname
    }
}

let jsonString = """
{
    "name": "John"
}
"""

do {
    let jsonData = jsonString.data(using: .utf8)! // this can't fail
    let jsonDecoder = JSONDecoder()
    let decodedPerson = try jsonDecoder.decode(Person.self, from: jsonData)
    print(decodedPerson.name) // Output: "John"

    let jsonEncoder = JSONEncoder()
    jsonEncoder.outputFormatting = .prettyPrinted
    let encodedData = try jsonEncoder.encode(decodedPerson)
    let encodedString = String(data: encodedData, encoding: .utf8)! // This won't fail either
    print(encodedString)
} catch {
    print(error)
}

The output is:

John
{
"fullname" : "John"
}


Just to reiterate. This is a bad idea. Your Person class, with these changes, can't decode the JSON that it encodes. Are you really sure that is what you want?