1

Lets say I have these classes:

// serialize this enum value as string
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
enum AnimalType
{
    Dog,
    Cat
}

abstract class Animal
{
    public abstract AnimalType AnimalType { get; }
}

class Dog : Animal
{
    public override AnimalType AnimalType => AnimalType.Dog;
}

class Cat : Animal
{
    public override AnimalType AnimalType => AnimalType.Cat;
}

class Person
{
    public Animal Pet { get; set; }
}

Then I would be able to serialize a Person but I will not be able to deserialize it:

var person = new Person();
person.Pet = new Dog();

// this works. It outputs: {"Pet":{"AnimalType":"Dog"}}
var json = System.Text.Json.JsonSerializer.Serialize(person);

// this does not work! and it makes sense
var clone = System.Text.Json.JsonSerializer.Deserialize<Person>(json);

In order to make the previous code work I will have to create this class:

class AnimalConverter : System.Text.Json.Serialization.JsonConverter<Animal>
{
    public override Animal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // I am assuming the animal is a dog
        return System.Text.Json.JsonSerializer.Deserialize<Dog>(ref reader);
    }

    public override void Write(Utf8JsonWriter writer, Animal value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

and now this code will work:

var person = new Person();
person.Pet = new Dog();

// I am able to serialize
var json = System.Text.Json.JsonSerializer.Serialize(person);

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new AnimalConverter());

// now this works but only if Person has a Dog
var clone = System.Text.Json.JsonSerializer.Deserialize<Person>(json, options);

As you can see this code only works when the person has a Dog as a pet.

  • How can I make it smart so that it knows how to desrialize it? I have tried playing with Utf8JsonReader reader and I am not able to seek and change position. It seems that my only option is to parse the whole document manually.

It will be great if I could perform a peek operation just to see what kind of object it is and then deserialize it based on that


Edit

Question explained in a more simple way:

I have a asp.net web API where a user sends me the json object:

{"Pet":{"AnimalType":"Dog"}}

How can I deserialize that into the correct c# object?

Tono Nam
  • 34,064
  • 78
  • 298
  • 470
  • 1
    Forget about System.Text.Json it's only good for "hello world". Try Newtonsoft.Json. This is my link https://stackoverflow.com/a/71398251/11392290 – Serge Mar 11 '22 at 02:58
  • Thanks Serge that works amazing. The jason I am trying to parse I receive it through my API so I have to make it work with json that does not have types :/ – Tono Nam Mar 11 '22 at 03:24
  • IMHO, you can use the property names as a type. Just create a simple function that will select type depending on property name. – Serge Mar 11 '22 at 03:29
  • Yeah but if I read the property type which I can, then I will not be able to deserialize the object using `return System.Text.Json.JsonSerializer.Deserialize(ref reader);` because `reader` will be in the wrong position because I have read something. It has a buffer that is not seekable and I cannot move the position of the buffer. You are correct in that I have to use Newtonsoft instead of system.text.json. I will give it a tryi with Newtonsoft – Tono Nam Mar 11 '22 at 03:44

1 Answers1

-3

Finally was able to solve it using Newtonsoft instead of System.Text.Json.

I had to add some property that help me identify the class. So I created this enum:

public enum AnimalType
{
    Dog,
    Cat
}

Now my classes look like this:

public abstract class Animal
{
    //[JsonConverter(typeof(CustomConverter))]
    public abstract AnimalType AnimalType { get; }

    public string? AnimalName { get; set; }
}

public class Dog : Animal
{
    public override AnimalType AnimalType => AnimalType.Dog;

    public string DogName { get; set; } = string.Empty;        
}

public class Cat : Animal
{
    public override AnimalType AnimalType => AnimalType.Cat;

    public string CatName { get; set; } = string.Empty;
}


public class Person
{
    public Animal Pet { get; set; }
}

And this converter enabled me to deserialize a Person:

public class AnimalConverter : JsonConverter<Animal>
{
    public override Animal? ReadJson(JsonReader reader, Type objectType, Animal? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        if (jObject is null)
            return null;

        var animalType = jObject.GetValue("AnimalType", StringComparison.OrdinalIgnoreCase)?.Value<string>();

        if (animalType == null)
            throw new Exception();

        var animalTypeValue = Enum.Parse<AnimalType>(animalType);

        switch (animalTypeValue)
        {
            case AnimalType.Dog:
                return jObject.ToObject<Dog>();
            case AnimalType.Cat:
                return jObject.ToObject<Cat>();
            default:
                break;
        }

        throw new NotImplementedException();
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, Animal? value, JsonSerializer serializer)
    {
        throw new Exception("This should never be called because CanWrite is set to false");
    }
}

Make sure you specify to use that converter on your asp.net core web application as:

// use this converters
var mvcBuilder = builder.Services.AddControllers().AddNewtonsoftJson(c =>
{    
    c.SerializerSettings.Converters.Add(new StringEnumConverter());
    c.SerializerSettings.Converters.Add(new AnimalConverter());
});

And use this NuGet packages:

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.3" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
        <PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.3.0" />
    </ItemGroup>
Tono Nam
  • 34,064
  • 78
  • 298
  • 470