来自 ASP.NET Core API 的 JSON 响应中缺少派生类型的属性

Derived type's properties missing in JSON response from ASP.NET Core API

我的 ASP.NET Core 3.1 API 控制器的 JSON 响应缺少属性。当属性使用派生类型时会发生这种情况;在派生类型中定义但不在基/接口中定义的任何属性都不会序列化为 JSON。似乎在响应中缺乏对多态性的支持,就好像序列化是基于属性的定义类型而不是其运行时类型一样。如何更改此行为以确保 JSON 响应中包含所有公共属性?

示例:

我的 .NET Core Web API 控制器返回此对象,该对象具有接口类型的属性。

1
2
3
4
5
6
7
8
    // controller returns this object
    public class Result
    {
        public IResultProperty ResultProperty { get; set; }   // property uses an interface type
    }

    public interface IResultProperty
    { }

这是一个派生类型,它定义了一个名为 Value.

的新公共属性

1
2
3
4
    public class StringResultProperty : IResultProperty
    {
        public string Value { get; set; }
    }

如果我像这样从我的控制器返回派生类型:

1
2
3
    return new MainResult {
        ResultProperty = new StringResultProperty { Value ="Hi there!" }
    };

然后实际响应包含一个空对象(缺少 Value 属性):

enter

1
2
3
    {
       "ResultProperty": {"Value":"Hi there!" }
    }

n


我最终创建了一个自定义 JsonConverter(System.Text.Json.Serialization 命名空间),它强制 JsonSerializer 序列化为对象的运行时类型。请参阅下面的解决方案部分。它很长,但效果很好,并且不需要我在 API 设计中牺牲面向对象的原则。

一些背景知识:Microsoft 有一个 System.Text.Json 序列化指南,其中有一个标题为"序列化派生类的属性"的部分,其中包含与我的问题相关的良好信息。特别是它解释了为什么派生类型的属性没有序列化:

This behavior is intended to help prevent accidental exposure of data
in a derived runtime-created type.

如果这不是您关心的问题,那么可以通过显式指定派生类型或指定 object,在对 JsonSerializer.Serialize 的调用中覆盖该行为,例如:

1
2
3
4
5
    // by specifying the derived type
    jsonString = JsonSerializer.Serialize(objToSerialize, objToSerialize.GetType(), serializeOptions);

    // or specifying 'object' works too
    jsonString = JsonSerializer.Serialize<object>(objToSerialize, serializeOptions);

要使用 ASP.NET Core 完成此操作,您需要挂钩到序列化过程。我使用自定义 JsonConverter 执行此操作,该 JsonConverter 调用 JsonSerializer.Serialize 上面显示的一种方式。我还实现了对反序列化的支持,虽然在原始问题中没有明确要求,但无论如何几乎总是需要。 (奇怪的是,只支持序列化而不支持反序列化被证明是很棘手的。)

解决方案

我创建了一个基类 DerivedTypeJsonConverter,它包含所有的序列化


n


这是预期的结果。当你这样做时,你正在向上转换,所以将被序列化的是向上转换的对象,而不是实际的派生类型。如果您需要派生类型的东西,那么它必须是属性的类型。出于这个原因,您可能想要使用泛型。换句话说:

1
2
3
4
5
public class Result<TResultProperty>
    where TResultProperty : IResultProperty
{
    public TResultProperty ResultProperty { get; set; }   // property uses an interface type
}

然后:

1
2
3
return new Result<StringResultProperty> {
    ResultProperty = new StringResultProperty { Value ="Hi there!" }  
};


n


n


n