Wow, thanks a lot for looking at it and for such a detailed and deep analysis!
It is indeed more complicated than might appear on the surface.
Would it help if the decoder API had a new function that accepted a value instead of a type? Then client would pass the already created and filled "template" value, some of the fields of that value will get overridden from JSON while the rest fields will stay intact. Perhaps that should work only for "var" labeled fields, not "let".
Below is a quick and dirty decoder prototype idea that exploits this direction. It needs "offset_of" and "size_of" that I have no idea how to make (short of generating them manually with a switch of MemoryLayout.offset / size calls... but that in turn would be the boilerplate we were trying to get rid of to begin with!)
// a quick & dirty decoder prototype. only POD types for now.
// here a value is passed as a parameter, not a type
// value is used as an initial state
// fields present in JSON will override fields those in value
// fields absent in json will be left intact
func decode<T>(_ value: T, from data: Data) -> T {
let dict = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
var value = value
withUnsafeMutableBytes(of: &value) {
let pointer = $0.baseAddress!
for child in Mirror(reflecting: value).children {
let name = child.label!
if let any = dict[name] {
let offset = offset_of(field: name, inType: T.self)!
let size = size_of(field: name, inType: T.self)!
var val: T = convert(fromAny: any)
memmove(pointer + offset, &val, size)
}
}
}
}
func offset_of<T>(field: String, inType: T.Type) -> Int? {
fatalError("TODO")
}
func size_of<T>(field: String, inType: T.Type) -> Int? {
fatalError("TODO")
}
func convert<T>(fromAny: Any) -> T {
// convert any to T here.
// e.g. "1.0" <-> 1, Int16 <-> Int, Float <-> Double, etc
fatalError("TODO")
}
Instead of offset_of
/ size_of
I could have used child.offset
and child.size
... if there was such a thing and convert
is trivial in comparison.