Reading foreign JSON while preserving its original types

The new FoundationEssentials module contains JSON decoder that is suitable for decoding JSON of a known structure that directly maps to an application data type (structure). There are situations where one needs to read a foreign JSON of an unknown, or rather semi-known foreign structure as-is, without converting it necessarily into native types immediately and automatically. The foreign structure is examined first and then it is decided what to do with it based on its content, or to report (typically multiple) issues about the structure.

The original Foundation has the JSONSerialisation.jsonObject() → Any function. The downside of JSONSerialisation.jsonObject() is, that it does not seem to preserve the original data type. NSValue is opaque type therefore "10.0" is presented as both int and double; bool values (true and false) are presented as both bools and ints.

The new Foundation(Preview) does not yet have JSONSerialisation-equivalent functionality.

The API I am looking for would be some low-level direct representation/view of the JSON structure, which would preserve information about the original data types. It does not have to provide core library types directly, wrapped value are just fine.

Is there any planned equivalent of the JSONSerialisation functionality (with preserving type information) in the new Foundation? If yes, where I can read more about it?

2 Likes

I can't count how many times I've seen these kinds of wrappers:

enum JSONValue {
    case object([String: JSONValue])
    case array([JSONValue])
    case number(Double)
    case string(String)
    case bool(Bool)
    case null
}

I take their prevalence as a good sign that this is something we should consider standardizing.

4 Likes

This is not ideal, but the objcType property on NSValue should identify the type the NSNumber was constructed with. The scheme is in Apple’s legacy docs and should be stable for all time, and I believe it’s followed even in swift-foundation (though I didn’t actually check):

EDIT: I am wrong-ish, swift-foundation does not use NSNumber for this on non-Apple platforms.

2 Likes

swift-corelibs-foundation does.

Is that possible at all? For example you can have 1.23 in JSON file but was it Double or Float or CGFloat? Ditto for 42, is that UInt8 or Int, etc? JSON format doesn't carry that information.

I suspect all of them will show up as double, which is correct because JSON doesn’t consider integers a distinct type from floating-point numbers, but also incorrect because JSON does not impose a limit on the bits and precision of numbers (even though JavaScript does). Booleans will be distinct, at least.

2 Likes

Yes, I use this extension:

extension NSNumber {
    fileprivate var isBool: Bool {
        // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of
        // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22).
        String(cString: objCType) == "c"
    }
}
1 Like