0

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.

9
  • This is possible by overriding Bar's func encode(to:) method and creating your own CodingKeys. However, this format can be somewhat ambiguous — why are you looking to do it this way? What if you need to add further properties in the future? Commented Feb 10, 2018 at 7:00
  • 1
    you mean Foo(name: "foo", bars: [bar1, bar2]) Commented Feb 10, 2018 at 7:06
  • @ItaiFerber That's just the requirement for now :). I was thinking CodingKeys only allows renaming or omitting properties, I'll explore more. Commented Feb 10, 2018 at 7:14
  • @LeoDabus Just edited the question. Thanks! Commented Feb 10, 2018 at 7:14
  • 1
    @justintime If you write your own encode(to:) you can define CodingKeys however you need to and use them however you like — in this case you can define a struct which conforms to CodingKey and can take on any String value Commented Feb 10, 2018 at 7:16

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

@LeoDabus Correct — you can apply the same treatment to Foo as well for the same effect. Will expand to include that.
Yup that's what I came up with, does the trick nicely! Cuts down a lot of extra code. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.