1

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.

linus_hologram
  • 1,595
  • 13
  • 38

1 Answers1

4

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)
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thanks for your help! – linus_hologram Dec 09 '19 at 18:31
  • This should not be necessary. You can call `super.init(from: decoder)` and `super.encode(to: encoder)` directly, without going through a `superEncoder` or `superDecoder`, or having to encode your supertypes properties. – Itai Ferber Dec 09 '19 at 18:56
  • @ItaiFerber is that so? Ok then, edited. I remember codable not working well with inheritance, but maybe my memory is confused. Has it always been this way? – Sweeper Dec 09 '19 at 19:03
  • @Sweeper This has always been the case, and the API works this way by design. What you might be remembering is that subclasses of `Codable` classes inherit their superclass's implementation, rather than re-synthesizing a new one. – Itai Ferber Dec 09 '19 at 19:13
  • BTW, caveat to this approach: this is only valid to do when both you and your superclass use the same type of encoding container. If you use a keyed container and your superclasses uses an unkeyed container (or some other combination), you'll hit an assertion. (This is why this method is not preferred by default, and why nesting is generally recommended.) – Itai Ferber Dec 09 '19 at 19:15