justintime
justintime

Reputation: 372

Custom structure using jSONEncoder

Want to encode an object into a custom structure using JSONEncoder+Encodable.

struct Foo: Encodable {
   var name: String?
   var bars: [Bar]?
}

struct Bar: Encodable {
   var name: String?
   var value: String?
}

let bar1 = Bar(name: "bar1", value: "barvalue1")
let bar2 = Bar(name: "bar2", value: "barvalue2")
let foo = Foo(name: "foovalue", bars: [bar1, bar2])

Default approach of encoding foo gives:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(foo)
print(String(data: data, encoding: .utf8)!)

Output:

{
   "name": "foovalue",
   "bars": [
      {
         "name": "bar1",
         "value": "barvalue1"
      },
      {
         "name": "bar2",
         "value": "barvalue2"
      }
   ]
}

In the custom output I'd like to use the value of property name as the key, and the values of rest as the value for the mentioned key. The same will be applicable for nested objects. So I'd expect the output to be:

{
    "foovalue": [
       {
          "bar1": "barvalue1"
       },
       {
          "bar2": "barvalue2"
       }
     ]
}

Question is whether Encodable/JSONEncoder supports this. Right now I just process the the first output dictionary and restructure it by iterating the keys.

Upvotes: 0

Views: 280

Answers (1)

Itai Ferber
Itai Ferber

Reputation: 29764

If you’d like to keep Foo and Bar Encodable, you can achieve this by providing a custom encode(to:) that uses a specific coding key whose value is name:

private struct StringKey: CodingKey {
    let stringValue: String
    var intValue: Int? { return nil }
    init(_ string: String) { stringValue = string }
    init?(stringValue: String) { self.init(stringValue) }
    init?(intValue: Int) { return nil }
}

struct Foo: Encodable {
    var name: String
    var bars: [Bar]

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

struct Bar : Encodable {
    var name: String
    var value: String

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

StringKey can take on any String value, allowing you to encode arbitrarily as needed.

Upvotes: 2

Related Questions