I'm working on an implementation of Codable
for an enum
type with possible associated values. Since these are unique to each case, I thought I could get away with outputting them without keys during encoding, and then simply see what I can get back when decoding in order to restore the correct case.
Here's a very much trimmed down, contrived example demonstrating a sort of dynamically typed value:
enum MyValueError : Error { case invalidEncoding }
enum MyValue {
case bool(Bool)
case float(Float)
case integer(Int)
case string(String)
}
extension MyValue : Codable {
init(from theDecoder:Decoder) throws {
let theEncodedValue = try theDecoder.singleValueContainer()
if let theValue = try? theEncodedValue.decode(Bool.self) {
self = .bool(theValue)
} else if let theValue = try? theEncodedValue.decode(Float.self) {
self = .float(theValue)
} else if let theValue = try? theEncodedValue.decode(Int.self) {
self = .integer(theValue)
} else if let theValue = try? theEncodedValue.decode(String.self) {
self = .string(theValue)
} else { throw MyValueError.invalidEncoding }
}
func encode(to theEncoder:Encoder) throws {
var theEncodedValue = theEncoder.singleValueContainer()
switch self {
case .bool(let theValue):
try theEncodedValue.encode(theValue)
case .float(let theValue):
try theEncodedValue.encode(theValue)
case .integer(let theValue):
try theEncodedValue.encode(theValue)
case .string(let theValue):
try theEncodedValue.encode(theValue)
}
}
}
let theEncodedValue = try! JSONEncoder().encode(MyValue.integer(123456))
let theEncodedString = String(data: theEncodedValue, encoding: .utf8)
let theDecodedValue = try! JSONDecoder().decode(MyValue.self, from: theEncodedValue)
However, this is giving me an error during the encoding stage as follows:
"Top-level MyValue encoded as number JSON fragment."
The issue appears to be that, for whatever reason, the JSONEncoder
won't allow a top-level type that isn't a recognised primitive to be encoded as a single primitive value. If I change the singleValueContainer()
to an unkeyedContainer()
then it works just fine, except that of course the resulting JSON
is an array, not a single value, or I can use a keyed container but this produces an object with the added overhead of a key.
Is what I'm trying to do here impossible with a single value container? If not, is there some workaround that I can use instead?
My aim was to make my type Codable
with a minimum of overhead, and not just as JSON
(the solution should support any valid Encoder
/Decoder
).