首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >WebAssembly -使用System.Text.Json进行多态DTO反序列化

WebAssembly -使用System.Text.Json进行多态DTO反序列化
EN

Stack Overflow用户
提问于 2021-11-19 09:43:00
回答 2查看 691关注 0票数 1

Abp框架版本: 5.0.0-beta2 2,UI: Blazor WebAssembly

我试图在ABP框架中实现多态,以便能够在API后端和Blazor前端之间交换派生类,并且很难从多个方面反序列化JSON:

代码语言:javascript
运行
复制
// Output Dtos
public abstract class AnimalOutputDto : EntityDto<Guid>
{
  public string Name { get; set; }
}

public class CatOutputDto : AnimalOutputDto
{
  public string Name { get; set; }
  public string Color { get; set; }
}

// Input Dtos
public abstract class AnimalInputDto : EntityDto<Guid>
{
  public string Name { get; set; }
}

public class CatInputDto : AnimalInputDto
{
  public string Name { get; set; }
  public string Color { get; set; }
}

当将模型从Blazor前端传递到HTTPAPI时,我可以使用本文中描述的自定义JsonConverter正确地反序列化它们,我将其添加到HTTPAPI项目中,然后在HTTPAPI.Host项目的ConfigureServices方法中引用:

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0#support-polymorphic-deserialization

代码语言:javascript
运行
复制
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var configuration = context.Services.GetConfiguration();
            var hostingEnvironment = context.Services.GetHostingEnvironment();
            ..Usual configuration statements..
            ConfigureJsonConverters(context);
        }

        private void ConfigureJsonConverters(ServiceConfigurationContext context)
        {
            context.Services.AddControllers(options =>
            {
            }).AddJsonOptions(options => {
                options.JsonSerializerOptions.Converters.Add(new AnimalJsonConverter());
            });                
        }

当模型被传递回Blazor前端时,我可以使用Microsoft文章中也指定的类型的正确转换器来验证它是否被序列化。

但是,当Blazor接收到模型时,会抛出一个异常:它显然没有识别多态类型,而是试图反序列化抽象基类:

代码语言:javascript
运行
复制
Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported.

似乎我需要找到一种方法,在Blazor项目中注册相同的自定义JSON转换器类,就像在HttpApi.Host项目中那样。但是,我找不到任何关于如何做到这一点的文档。

有人对此有任何信息或指导吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-11-21 10:29:28

通过使用JsonConvert类和JsonConverter属性,我成功地做到了这一点。这种方式在ConfigureServices()方法中没有必要的配置。

  1. 将输入和输出DTO添加到我的.Application.Contracts项目中,并仅在基类上使用JsonConverter(typeof(MyConverterClass))属性对它们进行修饰(将该属性添加到子类将导致序列化程序中的循环)

  1. 添加了一个enum属性,该属性覆盖基类,从而表示派生类类型,充当鉴别器

  1. 在以下

行中创建了一个适当的转换器类(在与DTO相同的项目中)

DTO课程:

代码语言:javascript
运行
复制
    [JsonConvert(typeof(AnimalInputJsonConverter))]
    public abstract class AnimalInputDto : EntityDto<Guid>
    {
        public string Name { get; set; }    
        public virtual AnimalType AnimalType => AnimalType.NotSelected
    } 

    public class CatInputDto : AnimalInputDto
    {
        public override AnimalType AnimalType => AnimalType.Cat
        [.. more properties specific to Cat]
    }

    [JsonConvert(typeof(AnimalOutputJsonConverter))]
    public abstract class AnimalOutputDto : EntityDto<Guid>
    {
        public string Name { get; set; }    
        public virtual AnimalType AnimalType => AnimalType.NotSelected
    } 

    public class CatOutputDto : AnimalOutputDto
    {
        public override AnimalType AnimalType => AnimalType.Cat
        [.. more properties specific to Cat]
    }

转换器示例(输入和输出DTO之间的代码本质上是相同的)

代码语言:javascript
运行
复制
    public class AnimalInputDtoJsonConverter : JsonConverter<AnimalInputDto>
    {
        public override bool CanConvert(Type typeToConvert) =>
            typeof(AnimalInputDto).IsAssignableFrom(typeToConvert);

        public override AnimalInputDto Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            // Take a copy of the reader as we need to check through the object first before deserializing.
            Utf8JsonReader readerClone = reader;

            if (readerClone.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            AnimalType typeDiscriminator = AnimalType.NotSelected;
            string camelCasedPropertyName = 
                nameof(AnimalDto.AnimalType).ToCamelCase();

            // Loop through the JSON tokens. Look for the required property by name.
            while (readerClone.Read())
            {
                if (readerClone.TokenType == JsonTokenType.PropertyName && readerClone.GetString() == camelCasedPropertyName)
                {
                    // Move on to the value, which has to parse out to an enum
                    readerClone.Read();
                    if (readerClone.TokenType == JsonTokenType.Number)
                    {
                        int value = readerClone.GetInt32();
                        try 
                        {
                            typeDiscriminator = (AnimalType)value;
                            break;
                        }
                        catch (InvalidCastException)
                        {
                            throw new JsonException($"{value} is not a recognised integer representation of {typeof(AnimalType)}");
                        }
                    }
                }
            }

            AnimalInputDto target = typeDiscriminator switch
            {
                AnimalType.Cat => JsonSerializer.Deserialize<CatInputDto>(ref reader, options),
                _ => throw new NotSupportedException($"The supplied object is not a recognised derivative of {typeof(AnimalInputDto)}")
            };

            return target;
        }

        public override void Write(
            Utf8JsonWriter writer,
            AnimalInputDto value,
            JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(writer, value, value.GetType(), options);
        }
    }

此外,一种通用方法似乎是可能的,尽管这段代码没有被优化或性能测试,但我期望使用反射和实例化的对象使用Activator.CreateInstance()来检查其鉴别器的值会对性能造成影响。

请注意,下面假设区分器属性是枚举属性,并且派生类的此属性名与枚举类型完全相同:

用途如下:

代码语言:javascript
运行
复制
    [JsonConvert(typeof(PolymorphicJsonConverter<AnimalInputDto, AnimalType>))]
    public abstract class AnimalInputDto : EntityDto<Guid>
    {
        public string Name { get; set; }    
        public virtual AnimalType AnimalType => AnimalType.NotSelected
    } 

    ...

    public class PolymorphicJsonConverter<T, U> : JsonConverter<T>
        where T : EntityDto<Guid>
        where U : Enum
    {
        public string TypeDiscriminator { get; private set; }
        public string TypeDiscriminatorCamelCase { get; private set; }

        public List<Type> DerivableTypes { get; private set; }

        public PolymorphicJsonConverter()
            : base()
        {
            TypeDiscriminator = typeof(U).Name;
            TypeDiscriminatorCamelCase = TypeDiscriminator.ToCamelCase();
            DerivableTypes = new List<Type>();
            foreach (var domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                var assemblyTypes = domainAssembly.GetTypes()
                  .Where(type => type.IsSubclassOf(typeof(T)) && !type.IsAbstract);

                DerivableTypes.AddRange(assemblyTypes);
            }
        }

        public override bool CanConvert(Type typeToConvert) =>
            typeof(T).IsAssignableFrom(typeToConvert);

        public override T Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            // Take a copy of the reader as we need to check through the object first before deserializing.
            Utf8JsonReader readerClone = reader;

            if (readerClone.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            U typeDiscriminatorValue = (U)Enum.ToObject(typeof(U), 0);

            // Loop through the JSON tokens. Look for the required property by name.
            while (readerClone.Read())
            {
                if (readerClone.TokenType == JsonTokenType.PropertyName && readerClone.GetString() == TypeDiscriminatorCamelCase)
                {
                    // Move on to the value, which has to parse out to an enum
                    readerClone.Read();
                    if (readerClone.TokenType == JsonTokenType.Number)
                    {
                        int value = readerClone.GetInt32();
                        try
                        {
                            typeDiscriminatorValue = (U)Enum.ToObject(typeof(U), value);
                            break;
                        }
                        catch (InvalidCastException)
                        {
                            throw new NotSupportedException($"{value} is not a recognised integer representation of {typeof(U)}");
                        }
                    }
                }
            }

            T target = null;

            foreach(var dt in DerivableTypes)
            {
                var newInst = Activator.CreateInstance(dt);
                var propValue = (U)newInst.GetType().GetProperty(TypeDiscriminator).GetValue(newInst, null);
                if (propValue.Equals(typeDiscriminatorValue))
                {
                    target = (T)JsonSerializer.Deserialize(ref reader, dt, options);
                }
            }

            if (target == null)
            {
                throw new NotSupportedException($"The supplied object is not a recognised derivative of {typeof(T)}");
            }

            return target;
        }

        public override void Write(
            Utf8JsonWriter writer,
            T value,
            JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(writer, value, value.GetType(), options);
        }

    }

以上/进一步阅读的启示:https://getyourbitstogether.com/polymorphic-serialization-in-system-text-json/ https://vpaulino.wordpress.com/2021/02/23/deserializing-polymorphic-types-with-system-text-json/ https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0 https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverter-1?view=net-6.0 https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverterattribute?view=net-6.0

票数 1
EN

Stack Overflow用户

发布于 2021-11-19 10:16:29

使用System.Text.Json仍然有一些限制-请看这里:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson

尽管它有一个解决办法,多态序列化和反序列化似乎就是其中之一。

我认为您只能在Blazor侧使用Newtonsoft.Json

总是使用Newtonsoft.Json

如果要继续对所有类型使用Newtonsoft.Json库,可以在模块类的PreConfigureServices方法中将UseHybridSerializer设置为false:

PreConfigure(options => { options.UseHybridSerializer = false;});

参考资料:

  1. Deserialization of reference types without parameterless constructor is not supported
  2. https://docs.abp.io/en/abp/latest/JSON#abpjsonoptions
  3. https://docs.abp.io/en/abp/4.4/Migration-Guides/Abp-4_0#unsupported-types
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70032776

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档