Search code examples
jsonswiftcodabledecodableencodable

Swift encode class without nesting super class


Summary: I would like to encode all fields of my super class without nesting them in the json result.

Here's what I mean:

Let's say we have these structs:

struct Toy: Codable {
  var name: String
}

class BasicEmployee: Codable {
  var name: String
  var id: Int

  init(name: String, id: Int) {
    self.name = name
    self.id = id
  }
}

class GiftEmployee: BasicEmployee {
  var birthday: Date
  var toy: Toy

  enum CodingKeys: CodingKey {
    case employee, birthday, toy
  }

  init(name: String, id: Int, birthday: Date, toy: Toy) {
    self.birthday = birthday
    self.toy = toy
    super.init(name: name, id: id)
  }

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    birthday = try container.decode(Date.self, forKey: .birthday)
    toy = try container.decode(Toy.self, forKey: .toy)
    let baseDecoder = try container.superDecoder(forKey: .employee)
    try super.init(from: baseDecoder)
  }

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

    let baseEncoder = container.superEncoder(forKey: .employee)
    try super.encode(to: baseEncoder)
  }
}

Now we decide to encode a GiftEmployee object like so:

let encoder = JSONEncoder()
let decoder = JSONDecoder()

let giftEmployee = GiftEmployee(name: "John Appleseed", id: 7, birthday: Date(), toy: Toy(name: "Teddy Bear"))
let giftData = try encoder.encode(giftEmployee)
let giftString = String(data: giftData, encoding: .utf8)!

Printing out giftString gives us the following output:

{"toy":{"name":"Teddy Bear"},"employee":{"name":"John Appleseed","id":7},"birthday":597607945.92342305}

As you can see, all properties of our BasicEmployee super class are nested inside the "employee" json field.

However, I don't want that. I would like the output of giftString to be the following:

{"toy":{"name":"Teddy Bear"},"name":"John Appleseed","id":7,"birthday":597607945.92342305}

The properties of the BasicEmployee struct should not be nested and be on the same level as the encoded properties of the GiftEmployee struct.

Note

I am aware that I could avoid all the trouble by changing the structure of the structs, however, this is not a possibility right now.

I would greatly appreciate any help I could get on my issue.


Solution

  • You can call super.init(from:) and super.encode(to:):

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        birthday = try container.decode(Date.self, forKey: .birthday)
        toy = try container.decode(Toy.self, forKey: .toy)
       super.init(from: decoder)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(birthday, forKey: .birthday)
        try container.encode(toy, forKey: .toy)
        try super.encode(to: encoder)
    }