Open eiriktsarpalis opened 8 months ago
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis See info in area-owners.md if you want to be subscribed.
This would be great. I’ve had to define methods like
static object ToNormalObject(this JsonNode? value)
{
// basically the same implementation as OP
}
In my migrations, I’ve had types with properties of Dictionary<string, object?>
and due to the limitations described in this issue, a lot of the operations I was doing (mostly casting) went from “just working” to InvalidCastException
s.
Another workaround I did was making the property type JsonObject
and change all the uses of this property to use the JSON type. I didn’t like doing this because it exposed the implementation detail of serialization. But it’s the hardiest solution to the problem, and actually exposed some bugs 😄.
I didn’t like doing this because it exposed the implementation detail of serialization. But it’s the hardiest solution to the problem, and actually exposed some bugs 😄.
I think that comment is spot on. This functionality is popular simply because it's what Json.NET was doing but it is fundamentally compromised when it comes to round-tripping capability.
The biggest problem with fidelity I encountered was numbers being non-roundtrippable. In my opinion the best solution would be to subtype JsonValue
further for numbers, and add some numeric methods to this JsonNumber
type, ideally even some generic math.
As a JSON number, it would have to follow JavaScript rules, but I think it can be done.
That’s a separate issue from deserializing to an object
though.
I agree that a JsonNumber
type would be useful, however substituting it in the existing JsonNode
hierarchy would be very much a breaking change.
substituting it in the existing JsonNode hierarchy would be very much a breaking change.
How so? If JsonNumber
is a subtype of JsonValue
, then I don't see the problem unless there's some GetType() == typeof(JsonValue)
shenanigans somewhere.
I was incorrectly assuming that JsonValue<T>
is part of the public API surface, but it seems like it isn't.
Background and motivation
Branching off from the conversation in #29960 and #97801 to consider a potential built-in
object
deserializer that targets .NET primitive values as opposed to targeting the DOM types:JsonNode
orJsonElement
. The background is enabling users migrating off ofJson.NET
needing a quick way to supportobject
deserialization, provided that the deserialized object is "simple enough". This approach is known to create problems w.r.t. loss of fidelity when roundtripping, which is why it was explicitly ruled out when STJ was initially being designed. It is still something we might want to consider as an opt-in accelerator for users that do depend on that behaviour.This proposal would map JSON to .NET types using the following recursive schema:
null
.bool
values.int
,long
ordouble
.string
values.List<object?>
.Dictionary<string, object?>
.Here's a reference implementation of the above:
The reference implementation is intentionally simplistic and necessarily loses fidelity when it comes to its roundtripping abilities. A few noteworthy examples:
DateTimeOffset
,TimeSpan
andGuid
are not roundtripped, instead users get back the string representation of these values. This is done intentionally for consistency, since such a deserialization scheme cannot support all possible types that serialize to string.NaN
,PositiveInfinity
andNegativeInfinity
currently serialized as strings using the opt-inJsonNumberHandling.AllowNamedFloatingPointLiterals
flag are not roundtripped and are instead returned as strings.decimal.MaxValue
gets fit into adouble
representation).API Proposal
API Usage
Alternative Designs
Do nothing, have users write their own custom converters.
Risks
There is no one way in which such a "natural" converter could be implemented and there also is no way in which the implementation could be extended by users. There is a good risk that users will not be able to use the feature because they require that the converter is able to roundtrip
DateOnly
orUri
instances, in which case they would still need to write a custom converter from scratch.cc @stephentoub @bartonjs @tannergooding who might have thoughts on how primitives get roundtripped.