Search code examples
c#json.netsystem.text.json

Determine if Type isn't recognized in System.Text.Json Serialization


I'm trying to move from Newtonsoft.Json to System.Text.Json and I'm trying to find a way to check if all of my data has been "caught" and is being mapped correctly (re: PolymorphismOptions), since I've used a lot of generic/abstract/interface types.

Is there a way to either:

  1. Determine the type that it's trying to convert at the moment? The GetTypeInfo() in JsonTypeInfoResolver seems to only return the "base" type that it finds.

  2. Throw an exception or error of some kind, IFF it finds an issue?

Thank you in advance!


Solution

  • I don't know of any way to get the actual type being serialized when the contract for the declared type is being constructed.

    However, you can force force System.Text.Json to automatically throw an exception when serializing an unexpected derived type. To do so, you will need to create a JsonTypeInfo modifier that automatically sets JsonPolymorphismOptions.UnknownDerivedTypeHandling to FailSerialization for all relevant non-sealed types:

    public static partial class JsonExtensions
    {
        public static Action<JsonTypeInfo> SetupUnknownDerivedTypeHandling(bool objectsOnly = false) =>
            (typeInfo) =>
        {
            // Polymorphism using type discriminators is only supported for type hierarchies that use the default converters for objects, collections, and dictionary types.
            if (typeInfo.Kind == JsonTypeInfoKind.None || typeInfo.Type.IsSealed || typeInfo.Type == typeof(object))
                return;
            if (objectsOnly && typeInfo.Kind != JsonTypeInfoKind.Object)
                return;
            // TODO: decide whether to fail serialization even for types marked with
            // [JsonPolymorphic(UnknownDerivedTypeHandling = FallBackToBaseType or FallBackToNearestAncestor)]
            if (typeInfo.PolymorphismOptions == null)
            {
                typeInfo.PolymorphismOptions = new() { DerivedTypes = { new(typeInfo.Type) } };
                // TODO: decide whether to fail serialization even for types marked with
                // [JsonPolymorphic(UnknownDerivedTypeHandling = FallBackToBaseType or FallBackToNearestAncestor)]
                // If so, move the setting of UnknownDerivedTypeHandling out of this if statement.
                typeInfo.PolymorphismOptions.UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization;
            };
        };
    }
    

    Then set up your options e.g. as follows:

    var options = new JsonSerializerOptions()
    {
        // Configure either a DefaultJsonTypeInfoResolver or some JsonSerializerContext and add the required modifier:
        TypeInfoResolver = new DefaultJsonTypeInfoResolver() 
            .WithAddedModifier(JsonExtensions.SetupUnknownDerivedTypeHandling()),
        // Set other options as required.
    };
    

    Notes:

    • Since System.Text.Json always serializes the full concrete type for references declared as object, I do not throw an exception in that case.

    • It is possible for polymorphic types to configure specific behavior for JsonPolymorphicAttribute.UnknownDerivedTypeHandling, with FailSerialization being the default. From the docs:

      JsonUnknownDerivedTypeHandling value Effect
      FailSerialization An object of undeclared runtime type will fail polymorphic serialization. The default.
      FallBackToBaseType An object of undeclared runtime type will fall back to the serialization contract of the base type.
      FallBackToNearestAncestor An object of undeclared runtime type will revert to the serialization contract of the nearest declared ancestor type. Certain interface hierarchies are not supported due to diamond ambiguity constraints.

      If some of your types have statically configured behavior other than FailSerialization and you want to override that, you can do so by moving the setting outside the if (typeInfo.PolymorphismOptions == null) check.

    • Personally I don't recommend failing serialization for unexpected collection or dictionary types. It's common for properties to be declared as IList<T> or IDictionary<TKey,TValue> and be implemented with some concrete type. If you pass objectsOnly = false an exception will be thrown in such cases.

    Demo fiddle here.