1

Normally if I have a struct like this:

struct Box: Codable {
  let id: String

  /// This is an expression (e.g. `x + 3`) rather than a number.
  let height: String
}

It would get encoded as JSON as follows:

{
  "id": "box1",
  "height": "x + 3"
}

The problem is that I want to introduce a new wrapper type Expression to make it obvious that I shouldn't be using normal strings in this property:

struct Box: Codable {
  let id: String
  let height: Expression
}

struct Expression: Codable {
  let string: String
}

The API is now more clear with this wrapper type, but the JSON is now nested:

{
  "id": "box1",
  "height": {
    "string": "x + 3"
  }
}

I'd like to remove this nesting so it looks like this again:

{
  "id": "box1",
  "height": "x + 3"
}

I'd prefer not to override anything on Box since:

  1. Box may have many other properties and I'd rather not have to maintain the encode/decode functions manually.
  2. Anywhere else that Expression is used should benefit from this non-nested container behavior without me writing more Codable boilerplate.

Therefore, I'd like to only modify Expression, and get it to output its encoded data without introducing an extra container.


I tried using container.superEncoder() as mentioned here, but that kept the same hierarchy and renamed "string" with "super".

Senseful
  • 86,719
  • 67
  • 308
  • 465

1 Answers1

2

First, notice that the standard implementation of Codable is as follows:

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

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

  enum CodingKeys: CodingKey {
    case string
  }
}

Notice how it adds a container insider the encoder/decoder.

Instead, what you want to do is interact with the encoder/decoder that is sent directly:

extension Expression: Codable {
  func encode(to encoder: Encoder) throws {
    try string.encode(to: encoder)
  }

  init(from decoder: Decoder) throws {
    string = try String(from: decoder)
  }
}

This will encode/decode the object as though the custom wrapper doesn't exist:

{
  "id": "box1",
  "height": "x + 3"
}
Senseful
  • 86,719
  • 67
  • 308
  • 465