Description
Background and motivation
When deserializing JSON into object
, either a JsonElement
or JsonObject
is created depending on JsonSerializerOptions.UnknownTypeHandling
. This is a proposal to add another value to this option supporting inferring the CLR type for primitive JSON values. For example, the type for "42" would be inferred as long
and a boxed long
would be created during deserialization. There are multiple reasons why this might be used.
This is the default behavior in Newtonsoft so people migrating might want the same behavior. Note that we have docs on how to get this behavior with custom converters. We can get a feel for the demand based the usage of this custom converter in public code. There are 322 results for this type on github and 52 on grep.
Another reason is that there is perf overhead to create JsonElement
and JsonObject
including more time to initialize the objects as well as the larger memory footprint (see #115816).
There are some downsides to type inference which is why it's not the default option in System.Text.Json. From our docs:
Type inference can be inaccurate. If the deserializer parses a JSON number that has no decimal point as a long, that might result in out-of-range issues if the value was originally serialized as a ulong or BigInteger. Parsing a number that has a decimal point as a double might lose precision if the number was originally serialized as a decimal.
/cc @eiriktsarpalis
API Proposal
namespace System.Text.Json.Serialization;
+ [Flags]
public enum JsonUnknownTypeHandling
{
JsonElement = 0,
JsonNode = 1,
+ InferPrimitiveType = 2,
}
Alternative without adding the flags attribute:
namespace System.Text.Json.Serialization;
public enum JsonUnknownTypeHandling
{
JsonElement = 0,
JsonNode = 1,
+ JsonElementAndPrimitiveTypeInference = 2,
+ JsonNodeAndPrimitiveTypeInference = 3,
}
API Usage
var opts = new JsonSerializerOptions
{
UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode | JsonUnknownTypeHandling.InferPrimitiveType
};
Console.WriteLine(JsonSerializer.Deserialize<object>("42", opts) is long); // True
Console.WriteLine(JsonSerializer.Deserialize<object>("\"2025-05-29T00:00:00-07:00\"", opts) is DateTime); // True
Console.WriteLine(JsonSerializer.Deserialize<object>("{}", opts) is JsonNode); // True
Console.WriteLine(JsonSerializer.Deserialize<Sample>("""{"Foo":"Bar"}""", opts).Foo is "Bar"); // True
class Sample { public object Foo { get; set; } }
Alternative Designs
No response
Risks
No response