Thanks for you answer!
However I do not quite understand how to fix it!
I am converting arbitrary encodable values into the closely matching dictionary/array counterparts. Given this input:
struct S: Codable {
let intValues: [Int: Int]
let stringValues: [String: Int]
}
struct Test: Codable {
var items: [[String : S]]
}
let value = Test(items: [["item" : S(intValues: [1 : 1, 2: 2], stringValues: ["1" : 1, "2": 2])]])
I am getting this output:
stringDictionary(
[
"items": TheValue.array(
[
TheValue.stringDictionary(
[
"item": TheValue.stringDictionary(
[
"intValues": TheValue.stringDictionary(
["1": TheValue.int(1), "2": TheValue.int(2)]
),
"stringValues": TheValue.stringDictionary(
["1": TheValue.int(1), "2": TheValue.int(2)]
)
]
)
]
)
]
)
]
)
Whilst obviously it should be this:
"intValues": TheValue.intDictionary(
[1: TheValue.int(1), 2: TheValue.int(2)]
),
"stringValues": TheValue.stringDictionary(
["1": TheValue.int(1), "2": TheValue.int(2)]
),
How do I do this properly?
Attaching the whole test app
import Foundation
import Combine
enum TheValue {
case null
case bool(Bool)
case string(String)
case int(Int)
case stringDictionary([String: TheValue])
case intDictionary([Int: TheValue])
case array([TheValue])
var stringDictionary: [String: TheValue]? {
switch self {
case .stringDictionary(let value): value
default: nil
}
}
var intDictionary: [Int: TheValue]? {
switch self {
case .intDictionary(let value): value
default: nil
}
}
var array: [TheValue] {
switch self {
case .array(let value): value
default: fatalError()
}
}
}
class Enc: Encoder {
var value: TheValue = .null
var codingPath: [CodingKey]
var userInfo: [CodingUserInfoKey : Any] { fatalError() }
init(codingPath: [CodingKey] = []) {
self.codingPath = codingPath
}
func container<Key: CodingKey>(keyedBy keyType: Key.Type) -> KeyedEncodingContainer<Key> {
value = .stringDictionary([:])
let container = MyKeyedEncodingContainer<Key>(codingPath: codingPath, encoder: self)
return KeyedEncodingContainer(container)
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
value = .array([])
return MyUnkeyedEncodingContainer(encoder: self)
}
func singleValueContainer() -> SingleValueEncodingContainer {
value = .null
return MySingleValueEncodingContainer(encoder: self)
}
}
struct MyKeyedEncodingContainer<Key: CodingKey>: KeyedEncodingContainerProtocol {
let codingPath: [CodingKey]
let encoder: Enc
mutating func encodeNil(forKey key: Key) throws { fatalError() }
mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
var dictionary = encoder.value.stringDictionary ?? [:]
encoder.value = .null
try value.encode(to: encoder)
dictionary[key.stringValue] = encoder.value
encoder.value = .stringDictionary(dictionary)
}
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> { fatalError() }
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { fatalError() }
mutating func superEncoder() -> Encoder { fatalError() }
mutating func superEncoder(forKey key: Key) -> Encoder { fatalError() }
}
struct MyUnkeyedEncodingContainer: UnkeyedEncodingContainer {
let encoder: Enc
var codingPath: [any CodingKey] { fatalError() }
var count: Int { fatalError() }
mutating func encodeNil() throws { fatalError() }
mutating func encode<T: Encodable>(_ value: T) throws {
var array = encoder.value.array
encoder.value = .null
try value.encode(to: encoder)
array.append(encoder.value)
encoder.value = .array(array)
}
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey { fatalError() }
mutating func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { fatalError() }
mutating func superEncoder() -> any Encoder { fatalError() }
}
struct MySingleValueEncodingContainer: SingleValueEncodingContainer {
let encoder: Enc
var codingPath: [CodingKey] { fatalError() }
mutating func encodeNil() throws { encoder.value = .null }
mutating func encode(_ value: Bool) throws { encoder.value = .bool(value) }
mutating func encode(_ value: String) throws { encoder.value = .string(value) }
mutating func encode(_ value: Int) throws { encoder.value = .int(value) }
mutating func encode<T>(_ value: T) throws where T : Encodable {
try value.encode(to: encoder)
}
}
class MyTopLevelEncoder: TopLevelEncoder {
func encode<T: Encodable>(_ value: T) throws -> TheValue {
print(type(of: value))
let enc = Enc()
try value.encode(to: enc)
return enc.value
}
}
struct S: Codable {
let intValues: [Int: Int]
let stringValues: [String: Int]
}
struct Test: Codable {
var items: [[String : S]]
}
let value = Test(items: [["item" : S(intValues: [1 : 1, 2: 2], stringValues: ["1" : 1, "2": 2])]])
let e = MyTopLevelEncoder()
let res = try! e.encode(value)
print(res)
Edit: the fatalError() above are for the paths that are not triggered in this minimal example (one of my approaches which I call "fatalError driven development").
To put my question slightly differently: consider I want to implement something like JSON that allows integer dictionary keys. I am not after the text form specifically, but that would be a great analogy. How would I implement that? Desired output in terms of this hypothetical JSON² of the above input example would be:
{
"items": [
{
"item" : {
"intValues": {1: 1, 2: 2},
"stringValues": {"1" : 1, "2": 2} // these stay being strings!
}
}
]
}