I need to parse a json format where many fields contain values of base types, such as uint8, uint16, uint32, and [uint8] β encoded as 0x-prefixed hex-strings, since for those who work on the input fields, they usually deal with binary protocols which the json format refers to and the server developer does not want to add a post processor that converts all those to NUMBER.
That said, I ponder whether something like this would be convenient:
import Foundation
struct HexEncodedNumber<T: FixedWidthInteger & UnsignedInteger>: Decodable {
let value: T
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
guard string.starts(with: "0x") else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "HexEncodedNumber does not start with characters 0x")
}
guard let value = T(string.dropFirst(2), radix: 16) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "HexEncodedNumber does not represent a \(T.self)")
}
self.value = value
}
}
struct Record: Decodable {
let level: HexEncodedNumber<UInt8>
let identifier: HexEncodedNumber<UInt16>
}
let json = """
{
"level": "0x80",
"identifier": "0xf190"
}
"""
let data = json.data(using: .utf8)!
let record = try! JSONDecoder().decode(Record.self, from: data)
print(record)
That way I could ensure that β if the values pass the decoder β they're valid and could work with the individual fields by accessing their 'value' property. Does that make sense to you? Is there possibly an even more convenient way to realize something like this?
The alternative β having a separate set of model objects that are using the 'actual' types and get constructed out of the json codable model objects β thus using them only as an intermediate specification β does not sound very attractive to me.
This is honestly the best way to do it, although it is laborious. Decode Codable structs that match the JSON response exactly (you could use the HexEncodedNumber here) and then convert all those decoded items into the models object for your app.
If your app is simple / just for fun, you don't need to bother with the intermediaries β but for proper production usage it's the best route in regard to separation of concerns, resilience, maintainability and testability.
FWIW, now that Foundation (at least on macOS) supports JSON5, it's much more convenient to move to that format. Among other great features, JSON5 supports hexadecimal numbers.