首页 > 解决方案 > Json.Net - 如何在不明确的 JSON 中指定反序列化类型

问题描述

到目前为止,我似乎找不到一个好的答案,但我承认也许我不够聪明,无法知道要搜索的正确关键字。所以这里。

假设我有一个包含混合对象类型的集合:

var wishList = new List<WishListItem>
{
    new Car { Price = 78000, Make = "Tesla", Model = "S", Name = "Tesla Model S" },
    new Computer { Manufacturer = "Microsoft", Name = "Surface Pro 6", Price = 2000 },
    new PlatitudeIdea { Name = "World peace" }
};

作为一个内置在内存中的集合,我可以使用强制转换来根据它们的底层类型来处理这些对象:

foreach (var wishListItem in wishList)
{
    if (wishListItem is PlatitudeIdea platitude)
    {
        Console.WriteLine($"{platitude.Name} is a hopeless dream");
    }
    else if (wishListItem is IPriceable priceThing)
    {
        Console.WriteLine($"At {priceThing.Price}, {priceThing.Name} is way out of my budget");
    }
    else
    {
        Console.WriteLine($"I want a {wishListItem.Name}");
    }
}

如果我将它序列化为 JSON 数组,一切看起来都很好......

[
    { "Price": 78000, "Make": "Tesla", "Model": "S", "Name": "Tesla Model S" },
    { "Manufacturer ": "Microsoft", "Name": "Surface Pro 6", "Price": 2000 },
    { "Name": "World peace" }
]

...但是当我解析JSON 时,解析器显然无法准确判断每个元素最初是哪种类型,因此它只是尝试将它们解析为我所期望List的泛型参数 ( ) 中声明的最低级别类型WishListItem

parsedWishList[0] is WishListitem // returns true :)
parsedWishList[0] is Car // returns false :(

这是有道理的,只要将被序列化的成员声明为超类型或接口,您就可以得到这种行为。我希望能够为我的特定类添加一个特殊属性,指示正在序列化的对象的类型:

public class Car : WishListItem, IPriceable 
{
    public override string @type => "Car";
}

或者更好的是,作为类型属性:

[JsonSerializedType("Car")]
public class Car : WishListItem, IPriceable 
{
    // ...
}

然后,只要类型声明不明确,就会将其输出到 JSON...

[
    { "type": "Car", "Price": 78000, "Make": "Tesla", "Model": "S" },
    { "type": "Computer", "Manufacturer ": "Microsoft", "Name": "Surface Pro 6", "Price": 2000 },
    { "type": "Platitude", "Value": "World peace" }
]

...并且解析器将该对象反序列化为该类型:

parsedWishList[0] is Car // returns true :)

我能从谷歌收集到的最接近答案的事情可能是尝试玩一下CustomCreationConverter,看看这是否会有所帮助。但是我需要一个非常通用的答案,我可以编写一次并让它处理任意类型。

任何指针?

标签: c#jsonjson.net

解决方案


听起来您正在寻找TypeNameHandling设置。此设置将导致 Json.Net 将类型信息写入 JSON,以便将其反序列化回其原始类型。

如果需要自定义类型名称,可以使用自定义SerializationBinder类。

KnownTypesBinder这是基于文档中显示的示例的往返演示:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace SO54465235
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var wishList = new List<WishListItem>
            {
                new Car { Price = 78000, Make = "Tesla", Model = "S", Name = "Tesla Model S" },
                new Computer { Manufacturer = "Microsoft", Name = "Surface Pro 6", Price = 2000 },
                new Platitude { Name = "World peace" }
            };

            KnownTypesBinder knownTypesBinder = new KnownTypesBinder
            {
                KnownTypes = new List<Type> { typeof(Car), typeof(Computer), typeof(Platitude) }
            };

            string json = JsonConvert.SerializeObject(wishList, Formatting.Indented, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects,
                SerializationBinder = knownTypesBinder
            });

            Console.WriteLine(json);
            Console.WriteLine();

            List<WishListItem> items = JsonConvert.DeserializeObject<List<WishListItem>>(json, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects,
                SerializationBinder = knownTypesBinder
            });

            foreach (var wishListItem in wishList)
            {
                if (wishListItem is Platitude platitude)
                {
                    Console.WriteLine($"{platitude.Name} is a hopeless dream");
                }
                else if (wishListItem is IPriceable priceThing)
                {
                    Console.WriteLine($"At {priceThing.Price}, {priceThing.Name} is way out of my budget");
                }
                else
                {
                    Console.WriteLine($"I want a {wishListItem.Name}");
                }
            }
        }
    }

    public class KnownTypesBinder : ISerializationBinder
    {
        public IList<Type> KnownTypes { get; set; }

        public Type BindToType(string assemblyName, string typeName)
        {
            return KnownTypes.SingleOrDefault(t => t.Name == typeName);
        }

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

    class WishListItem
    {
        public string Name { get; set; }
    }

    interface IPriceable
    {
        int Price { get; set; }
        string Name { get; set; }
    }

    class Car : WishListItem, IPriceable
    {
        public string Make { get; set; }
        public string Model { get; set; }
        public int Price { get; set; }
    }

    class Computer : WishListItem, IPriceable
    {
        public string Manufacturer { get; set; }
        public int Price { get; set; }
    }

    class Platitude : WishListItem
    {

    }
}

输出:

[
  {
    "$type": "Car",
    "Make": "Tesla",
    "Model": "S",
    "Price": 78000,
    "Name": "Tesla Model S"
  },
  {
    "$type": "Computer",
    "Manufacturer": "Microsoft",
    "Price": 2000,
    "Name": "Surface Pro 6"
  },
  {
    "$type": "Platitude",
    "Name": "World peace"
  }
]

At 78000, Tesla Model S is way out of my budget
At 2000, Surface Pro 6 is way out of my budget
World peace is a hopeless dream

推荐阅读