【问题标题】:Json.NET - Deserialising interface property throws error "type is an interface or abstract class and cannot be instantiated"Json.NET - 反序列化接口属性引发错误“类型是接口或抽象类,无法实例化”
【发布时间】:2021-10-08 06:24:09
【问题描述】:

我有一个类,它的属性是一个接口。

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
} 

尝试使用 Json.NET 反序列化 Foo 类会给我一条错误消息,例如:

Could not create an instance of type ISomething. Type is an interface or abstract class and cannot be instantiated.

从类似的question 中,我可以看到使用TypeNameHandling = TypeNameHandling.Objects 将通过允许Json.NET 在序列化时包含.NET 类型名称来解决错误,从而知道它需要将对象反序列化为哪个具体类型之后。

但是,当使用 TypeNameHandling 以外的 TypeNameHandling.None 值从外部源反序列化 JSON 时,似乎存在 caution advised

TypeNameHandling.Objects 显然属于这一类。

有没有办法在不引入任何安全风险的情况下反序列化具体对象类型(实现接口)?

【问题讨论】:

  • @Ermiya Eskandary 有多个类扩展了这个ISomething 接口。因此,编写具体版本不是一个好主意。
  • 您只想反序列化到一个具体的类,还是在接口上有一个属性,您想用它来确定要反序列化的类,例如类型枚举?
  • 哪一个?额外的属性是什么?
  • @Ermiya Eskandary,它将有多种具体类型,我在接口上没有属性:/。此类是基础抽象类,将被许多类使用。因此,只需进行最少的更改。
  • 谢谢班迪,我在下面添加了答案:)

标签: c# .net json.net deserialization json-deserialization


【解决方案1】:

考虑到您没有字段来确定要转换为哪种具体类型,您唯一的解决方案就是您所说的TypeNameHandling = TypeNameHandling.Objects

但是,您可以缓解安全漏洞。

使用自定义 ISerializationBinder 验证传入的类型名称,因为它们在序列化期间将 .NET 类型解析为类型名称,在反序列化期间将类型名称解析为 .NET 类型:

ConsoleAppSerializationBinder.cs

public class ConsoleAppSerializationBinder : ISerializationBinder
{
    public Type BindToType(string? assemblyName, string typeName)
    {
        var resolvedTypeName = $"{typeName}, {assemblyName}";
        return Type.GetType(resolvedTypeName, true);
    }

    public void BindToName(Type serializedType, out string? assemblyName, out string? typeName)
    {
        assemblyName = null;
        typeName = serializedType.AssemblyQualifiedName;
    }
}

JsonSerializerSettings object 上的SerializationBinder 属性设置为您的活页夹实例,并使用与最初用于序列化数据的相同类型的活页夹进行反序列化。

var jsonSerializerSettings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    SerializationBinder = new ConsoleAppSerializationBinder()
};

这里有一些演示工作代码来演示用法:

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var jsonSerializerSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects,
                SerializationBinder = new ConsoleAppSerializationBinder()
            };

            var something1Input = new Foo
            {
                Number = 1,
                Thing = new Something1
                {
                    RandomNumber = 1,
                    RandomString = "test"
                }
            };

            var something2Input = new Foo
            {
                Number = 1,
                Thing = new Something2
                {
                    RandomNumber = 2,
                    RandomBool = true
                }
            };

            var something1Json = JsonConvert.SerializeObject(something1Input, jsonSerializerSettings);
            var something2Json = JsonConvert.SerializeObject(something2Input, jsonSerializerSettings);

            var something1 = JsonConvert.DeserializeObject<Foo>(something1Json, jsonSerializerSettings);
            var something2 = JsonConvert.DeserializeObject<Foo>(something2Json, jsonSerializerSettings);

            Console.WriteLine(something1.Thing.GetType());
            Console.WriteLine(something2.Thing.GetType());
        }

        public class Foo
        {
            public int Number { get; set; }

            public ISomething Thing { get; set; }
        }

        public interface ISomething
        {
            public int RandomNumber { get; set; }
        }

        public class Something1 : ISomething
        {
            public int RandomNumber { get; set; }
            public string RandomString { get; set; }
        }

        public class Something2 : ISomething
        {
            public int RandomNumber { get; set; }
            public bool RandomBool { get; set; }
        }

        public class ConsoleAppSerializationBinder : ISerializationBinder
        {
            public Type BindToType(string? assemblyName, string typeName)
            {
                var resolvedTypeName = $"{typeName}, {assemblyName}";
                return Type.GetType(resolvedTypeName, true);
            }

            public void BindToName(Type serializedType, out string? assemblyName, out string? typeName)
            {
                assemblyName = null;
                typeName = serializedType.AssemblyQualifiedName;
            }
        }
    }
}

输出:

ConsoleApp.Program+Something1
ConsoleApp.Program+Something2

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-01
    相关资源
    最近更新 更多