使用 System.Text.Json 序列化和反序列化包含复杂属性的对象

Serialize and Deserialize objects that contain complex properties using System.Text.Json

假设我有这些 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; }
}

然后我将能够序列化一个 Person 但我将无法反序列化它:

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);

为了使前面的代码正常工作,我必须创建这个 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();
    }
}

现在这段代码可以工作了:

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);

如您所见,此代码仅在该人将狗作为宠物时才有效。

如果我可以执行一个 peek 操作来查看它是什么类型的对象,然后根据它反序列化它,那就太好了


编辑

问题用更简单的方式解释:

我有一个 asp.net 网站 API,用户向我发送 json 对象:

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

如何将其反序列化为正确的 C# 对象?

终于能够使用 Newtonsoft 而不是 System.Text.Json 来解决它。

我必须添加一些 属性 来帮助我识别 class。所以我创建了这个枚举:

public enum AnimalType
{
    Dog,
    Cat
}

现在我的 class 看起来像这样:

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; }
}

这个转换器使我能够反序列化一个人:

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");
    }
}

确保您指定在 asp.net 核心网络应用程序上使用该转换器作为:

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

并使用这个 NuGet 包:

    <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>