1

I want to deserialize a JSON file into a List<Person> and I want to intercept the instances of Person that gets created (not the instance of List<Person> but this might be helpful too) is it possible to do it by implementing a custom IJsonTypeInfoResolver or deriving from DefaultJsonTypeInfoResolver?

I've tried to derive from DefaultJsonTypeInfoResolver and create a custom resolver by overriding GetTypeInfo but I'm not sure how to get the created instances of Person.

I've checked some of the properties and methods on JsonTypeInfo through debugging but I couldn't really understand what I should do or how I should use the API and the documentation seems lacking for my specific case, unfortunately.

Here is an example:

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    TypeInfoResolver = new JsonPersonResolver()
};

options.Converters.Add(new JsonDateTimeConverter());
options.Converters.Add(new JsonDateOnlyConverter());
options.Converters.Add(new JsonColorConverter());
options.Converters.Add(new JsonDecimalConverter());

var people = await JsonSerializer.DeserializeAsync<List<Person>>(stream, options);

sealed record Person
{
    public string FullName { get; set; }
    public int Age { get; set; }
}

sealed class JsonCustomResolver : DefaultJsonTypeInfoResolver
{
    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
    {
        var info = base.GetTypeInfo(type, options);

        // Do what?

        return info;
    }
}
iam3yal
  • 2,188
  • 4
  • 35
  • 44
  • After you Deserialize you get a list of people then you can use foreach to get each people why need to implement a Resolver – MichaelMao Dec 21 '22 at 11:26
  • Have you read this? https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-7/ – Palle Due Dec 21 '22 at 12:06
  • @MichaelMao That would be extremely inefficient to do after the fact, there's no need to iterate over the collection when somewhere it creates and adds all the instances and the example above is just that an example to demonstrate my issue but there are over million records per file, I can also create a custom collection and intercept the instances when they are added myself but this has other downsides for example I'd have to iterate over the properties again so it's not what I'm looking for, I know the alternatives and why I need a resolver just not how to work with it yet. – iam3yal Dec 21 '22 at 12:33
  • @PalleDue Yes as well as this https://stackoverflow.com/questions/74875289/im-using-system-text-json-and-i-want-to-intercept-the-instances-that-gets-creat but it doesn't help as I need to intercept the instances when they are created and while there are examples to modify properties and such I haven't seen anything related to what I'm looking for, it might not even be possible, I'm not sure but that's why I'm asking. – iam3yal Dec 21 '22 at 14:20
  • @Serge - the question seems fairly clear to me. They're looking for the equivalent of [In JSON.NET how to get a reference to every deserialized object?](https://stackoverflow.com/q/39300005/3744182) in System.Text.Json, restricted to the specific type `Person`. – dbc Dec 21 '22 at 19:12
  • @dbc I think there are several ways to reach a peson object and I don't think that it is really need to cath the type. I just try to understand what is the pourpose of this exersize. – Serge Dec 21 '22 at 19:21
  • @Serge I'm aware that alternatives/workarounds exist but not without downsides and I've noted this in my previous comment what I'm really after is possible with JSON.NET as noted by dbc. you don't need to understand anything beyond the example I provided above and I'm not going to explain to you what is the purpose because I'm sure your time is valuable as mine and it's not related to the discussion! I'm not here to get an advice, I'm here to get an answer and this is by no means an exercise. – iam3yal Dec 21 '22 at 22:48
  • @Serge I'm sure I expressed that to the best of my ability and that it's clear enough but if something isn't clear it's better to tell people how to improve their existing example or code as opposed to tell them they are posting strange looking code and diverge from the topic or go beyond the scope of what is discussed. – iam3yal Dec 21 '22 at 22:53

1 Answers1

3

You can get a callback every time an instance of Person is created by adding a DefaultJsonTypeInfoResolver modifier that invokes a custom OnDeserializing or OnDeserialized callback for the Person type.

First, define the following extension methods for DefaultJsonTypeInfoResolver:

public static class JsonExtensions
{
    public static DefaultJsonTypeInfoResolver AddOnDeserializing<T>(this DefaultJsonTypeInfoResolver resolver, Action<T> onDeserializing)
    {
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeof(T) == typeInfo.Type) // Or typeof(T).IsAssignableFrom(typeinfo.Type) if you prefer
                                   {
                                       if (typeInfo.OnDeserializing == null)
                                           typeInfo.OnDeserializing = (o) => onDeserializing((T)o);
                                       else
                                       {
                                           var old = typeInfo.OnDeserializing;
                                           typeInfo.OnDeserializing = (o) => { old(o); onDeserializing((T)o); };
                                       }
                                   }
                               });
        return resolver;
    }
    public static DefaultJsonTypeInfoResolver AddOnDeserialized<T>(this DefaultJsonTypeInfoResolver resolver, Action<T> onDeserialized)
    {
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeof(T) == typeInfo.Type) // Or typeof(T).IsAssignableFrom(typeinfo.Type) if you prefer
                                   {
                                       if (typeInfo.OnDeserialized == null)
                                           typeInfo.OnDeserialized = (o) => onDeserialized((T)o);
                                       else
                                       {
                                           var old = typeInfo.OnDeserialized;
                                           typeInfo.OnDeserialized = (o) => { old(o); onDeserialized((T)o); };
                                       }
                                   }
                               });
        return resolver;
    }
}

Now you will be able to capture the creation of all Person objects wherever they are created in your deserialized object graph by adding an appropriate Action<Person>:

List<Person> allPeopleCreatedAnywhere = new();
Action<Person> personCreated = p => { Console.WriteLine("Intercepted persion {0}", p); allPeopleCreatedAnywhere.Add(p); };
var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        // Use AddOnDeserializing if you want to intercept the Person before it is populated
        // Use AddOnDeserialized if you want to intercept the Person after it is populated.
        .AddOnDeserialized(personCreated)
        // Add other modifiers as required
        ,
    // Add other settings and converters as required
    PropertyNameCaseInsensitive = true,
};

var people = await JsonSerializer.DeserializeAsync<List<Person>>(stream, options);

Console.WriteLine("A total of {0} people were intercepted.", allPeopleCreatedAnywhere.Count);  

Notes:

  • As explained in the docs:

    Serialization callbacks are only supported for Object metadata.

    Thus you will only be able to get intercept instances of c# types that are serialized as JSON objects. If you try to intercept instances of types serialized as JSON arrays such as List<Person> or primitives such as DateTime with this technique, System.Text.Json will throw an exception.

  • I recommended this approach because it doesn't require a converter, and it works with objects that have parameterized constructors. An alternative might be to chain something to JsonTypeInfo.CreateObject, however that wouldn't work with parameterized constructors.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thank you so much, this helps a lot! I've seen these OnDeserializing and OnDeserialized properties but wasn't sure whether it's idiomatic or the correct approach to go about it, thanks. ;) – iam3yal Dec 21 '22 at 22:59
  • 1
    @eyalalonn - You're welcome. There isn't really a single "correct" approach here. I recommended this approach because it doesn't require a converter, and it works with objects that have parameterized constructors. Another alternative might be to chain something to [`JsonTypeInfo.CreateObject`](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.metadata.jsontypeinfo.createobject), however that wouldn't work with parameterized constructors. – dbc Dec 21 '22 at 23:03