Home > Back-end >  What is the purpose of the `inputType` parameter of `JsonSerializer.Serialize`
What is the purpose of the `inputType` parameter of `JsonSerializer.Serialize`

Time:01-28

System.Text.Json.JsonSerializer.Serialize is a set of overloads that serialize a C# object into json.

The non-generic overloads all share three parameters - object? value which is the object to serialize; System.Text.Json.JsonSerializerOptions? options, which allows configuring the serializer with respect to all kinds of choices, and Type inputType, which is what this question is about.

inputType is merely described as "The type of the value to convert." However, what does that actually mean and do? Is there a meaningful difference between typeof(object) in this context and value.GetType()?

I peeked into the code, but it quickly became clearly this isn't a simple matter; the type helps resolve a JsonTypeInfo, but e.g. typeof(object) is special-cased there.

I did a few quick-and dirty benchmarks:

using System.Security.Cryptography;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<JsonBench>();

sealed record Bla(string Foo, int Bar);

public class JsonBench
{
    readonly Bla value = new Bla("a", 2);
    
    [Benchmark]
    public string WithGenerics() => JsonSerializer.Serialize(value);

    [Benchmark]
    public string WithGetType() => JsonSerializer.Serialize(value, value.GetType());

    [Benchmark]
    public string WithObjectType() => JsonSerializer.Serialize(value, typeof(object));

    readonly Type cachedObject = typeof(object), cachedBla = typeof(Bla);

    [Benchmark]
    public string WithCachedGetType() => JsonSerializer.Serialize(value, cachedBla);
    
    [Benchmark]
    public string WithCachedObjectType() => JsonSerializer.Serialize(value, cachedObject);
}

...and for small objects there appears to be very slight (on the order of 10ns) overhead from using typeof(object). Is that it? Are there corner cases where this is more? If using value.GetType() is reliably faster... why does this choice even exist all?

In short: I'm not sure I understand the purpose of this Type inputType parameter.

Can anybody clarify what this is actually for?

CodePudding user response:

As stated in the docs, the inputType argument of JsonSerializer.Serialize(Object value, Type type, JsonSerializerOptions? options = default) specifies:

The type of the value to convert.

Specifically, inputType defines the type to use when determining the serialization contract for the incoming value:

  1. typeof(object) - The actual, concrete type of the incoming value is used.

  2. value.GetType() - Once again the actual, concrete type of the incoming value is used.

  3. Some other System.Type that value inherits from or implements -- this type will be used instead of the actual, concrete type.

E.g., consider the following type hierarchy:

public class Base
{
    public string BaseProperty { get; set; }
}

public class Derived : Base
{
    public string DerivedProperty { get; set; }
}

Then if you serialize an instance of Derived using typeof(Base):

var value = new Derived { BaseProperty = "hello", DerivedProperty = "there" };
var json = JsonSerializer.Serialize(value, typeof(Base));

Then the serializer will only serialize the properties of the specified base type Base:

{
  "BaseProperty" : "hello"
}

Demo here. You will get the same result if you do JsonSerializer.Serialize<Base>(value).

Since your sealed record Bla(string Foo, int Bar); does not have any derived or base types other than typeof(object), your benchmarks can't surface the third case.[1]

This is discussed in How to serialize properties of derived classes with System.Text.Json:

In versions prior to .NET 7, System.Text.Json doesn't support the serialization of polymorphic type hierarchies. For example, if a property's type is an interface or an abstract class, only the properties defined on the interface or abstract class are serialized, even if the runtime type has additional properties. The exceptions to this behavior are explained in this section.

...

To serialize the properties of the derived type... use one of the following approaches:

  • Call an overload of Serialize that lets you specify the type at run time.

  • Declare the object to be serialized as object.

Now, in .NET 7 polymorphic type hierarchies are supported, however if you don't mark your types with JsonDerivedTypeAttribute or use some other mechanism to inform the serializer of the expected derived types, the behavior falls back to the .NET 6 behavior.

Related questions:


[1] Eamon Nerbonne comments:

incidentally, that sealed record Bla does have base types, and those have the expected useless behavior when used here - JsonSerializer(new Bla("",1), typeof(IEquatable<Bla>)) == "{}".

  • Related