使用Newtonsoft.Json,如何将SerializationBinder和CustomResolver一起反序列化抽象/接口类型? [英] Using Newtonsoft.Json, how can I use a SerializationBinder and CustomResolver together to deserialize abstract/interface types?

查看:51
本文介绍了使用Newtonsoft.Json,如何将SerializationBinder和CustomResolver一起反序列化抽象/接口类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于非常有用的答案 我写了,但它引发了异常,请参见下文.

Building on this very helpful answer from Brian Rogers I wrote the this, but it throws an exception, see more below.

我重新编写了代码,因为不幸的是原始版本不能完全满足我的要求:我需要包含一个ISerializationBinder实现,因为我必须以不同的方式映射类型才能与IOC一起使用("Control)容器:

I re-wrote the code, because the original version unfortunately doesn't quite meet my requirements: I need to include an ISerializationBinder implementation, because I have to map types differently for use with an IOC ("Inversion Of Control") container:

  • 对于序列化,我需要将 component 类型(即class)绑定到类型别名.
  • 对于 de 序列化,我需要将类型别名绑定到 service 类型(即接口).
  • For serialization I need to bind a component type (i.e. class) to a type alias.
  • For deserialization I need to bind the type alias to a service type (i.e. interface).

下面是我的代码,其中包括与Brian的原始答案相比所更改的内容的评论.

Below is my code, including comments on what I changed compared to Brian's original answer.

主要

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

public class Program
{
    public static void Main()
    {
        Assembly thing = new Assembly
        {
            Name = "Gizmo",
            Parts = new List<IWidget>
        {
            new Assembly
            {
                Name = "Upper Doodad",
                Parts = new List<IWidget>
                {
                    new Bolt { Name = "UB1", HeadSize = 2, Length = 6 },
                    new Washer { Name = "UW1", Thickness = 1 },
                    new Nut { Name = "UN1", Size = 2 }
                }
            },
            new Assembly
            {
                Name = "Lower Doodad",
                Parts = new List<IWidget>
                {
                    new Bolt { Name = "LB1", HeadSize = 3, Length = 5 },
                    new Washer { Name = "LW1", Thickness = 2 },
                    new Washer { Name = "LW2", Thickness = 1 },
                    new Nut { Name = "LN1", Size = 3 }
                }
            }
        }
        };

        var settings = new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Objects,
            Formatting = Formatting.Indented,
            ContractResolver = new CustomResolver(),
            SerializationBinder = new CustomSerializationBinder(),
        };

        Console.WriteLine("----- Serializing widget tree to JSON -----");
        string json = JsonConvert.SerializeObject(thing, settings);
        Console.WriteLine(json);
        Console.WriteLine();

        Console.WriteLine("----- Deserializing JSON back to widgets -----");
        using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        using (StreamReader sr = new StreamReader(stream))
        using (JsonTextReader reader = new JsonTextReader(sr))
        {
            var serializer = JsonSerializer.Create(settings);

            var widget = serializer.Deserialize<IAssembly>(reader);
            Console.WriteLine();
            Console.WriteLine(widget.ToString(""));
        }
    }
}

更改:

  • 不是使用new JsonSerializer {...};,而是使用了JsonSerializer.Create工厂方法,该方法将JsonSerializerSettings作为参数,以避免多余的设置规范.我认为这不会产生任何影响,但是我想提一下.
  • 我在settings中添加了我的CustomSerializationBinder,用于反序列化和序列化.
  • Instead of using new JsonSerializer {...}; I used the JsonSerializer.Create factory method, which takes JsonSerializerSettings as a parameter, to avoid redundant settings specifications. I don't think this should have any impact, but I wanted to mention it.
  • I added my CustomSerializationBinder to the settings, used for both deserialization and serialization.

合同解析器:

public class CustomResolver : DefaultContractResolver
{
    private MockIoc _ioc = new MockIoc();

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        // This is just to show that the CreateObjectContract is being called
        Console.WriteLine("Created a contract for '" + objectType.Name + "'.");

        contract.DefaultCreator = () =>
        {
            // Use your IOC container here to create the instance.
            var instance = _ioc.Resolve(objectType);

            // This is just to show that the DefaultCreator is being called
            Console.WriteLine("Created a '" + objectType.Name + "' instance.");

            return instance;
        };
        return contract;
    }
}

更改:

  • DefaultCreator委托现在使用模拟IOC(请参见下文)创建所需的实例.
  • The DefaultCreator delegate now uses a mock IOC, see below, to create the required instance.

系列化活页夹:

public class CustomSerializationBinder : ISerializationBinder
{
    Dictionary<string, Type> AliasToTypeMapping { get; }
    Dictionary<Type, string> TypeToAliasMapping { get; }

    public CustomSerializationBinder()
    {
        TypeToAliasMapping = new Dictionary<Type, string>
        {
            { typeof(Assembly), "A" },
            { typeof(Bolt), "B" },
            { typeof(Washer), "W" },
            { typeof(Nut), "N" },
        };

        AliasToTypeMapping = new Dictionary<string, Type>
        {
            { "A", typeof(IAssembly) },
            { "B", typeof(IBolt) },
            { "W", typeof(IWasher) },
            { "N", typeof(INut) },
        };
    }

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        var alias = TypeToAliasMapping[serializedType];

        assemblyName = null;  // I don't care about the assembly name for this example
        typeName = alias;
        Console.WriteLine("Binding type " + serializedType.Name + " to alias " + alias);
    }

    public Type BindToType(string assemblyName, string typeName)
    {
        var type = AliasToTypeMapping[typeName];
        Console.WriteLine("Binding alias " + typeName + " to type " + type);
        return type;
    }
}

更改:

  • 根据我的要求更改了映射:
    • TypeToAliasMapping使用 component 类型(即类)进行序列化.
    • AliasToTypeMapping使用 service 类型(即接口)进行反序列化.
    • The mappings were changed according to my requirements:
      • TypeToAliasMapping uses the component type (i.e. class) for serializing.
      • AliasToTypeMapping uses the service type (i.e. interface) for deserializing.

      模拟IOC容器:

      public class MockIoc
      {
          public Dictionary<Type, Type> ServiceToComponentMapping { get; set; }
          public MockIoc()
          {
              ServiceToComponentMapping = new Dictionary<Type, Type>
              {
                  { typeof(IAssembly), typeof(Assembly)   },
                  { typeof(IBolt)    , typeof(Bolt)       },
                  { typeof(IWasher)  , typeof(Washer)     },
                  { typeof(INut)     , typeof(Nut)        },
              };
          }
      
          public object Resolve(Type serviceType)
          {
              var componentType = ServiceToComponentMapping[serviceType];
              var instance = Activator.CreateInstance(componentType);
      
              return instance;
          }
      }       
      

      这是新功能,仅将接口(在反序列化期间通过绑定获得)映射到一个类(用于创建实际实例).

      This is new and simply maps the interface (obtained by the binding during deserialization) to a class (used for creating an actual instance).

      示例类别:

      public interface IWidget
      {
          string Name { get; }
          string ToString(string indent = "");
      }
      
      public interface IAssembly : IWidget { }
      public class Assembly : IAssembly
      {
          public string Name { get; set; }
          public List<IWidget> Parts { get; set; }
      
          public string ToString(string indent = "")
          {
              var sb = new StringBuilder();
              sb.AppendLine(indent + "Assembly " + Name + ", containing the following parts:");
              foreach (IWidget part in Parts)
              {
                  sb.AppendLine(part.ToString(indent + "  "));
              }
              return sb.ToString();
          }
      }
      
      public interface IBolt : IWidget { }
      public class Bolt : IBolt
      {
          public string Name { get; set; }
          public int HeadSize { get; set; }
          public int Length { get; set; }
      
          public string ToString(string indent = "")
          {
              return indent + "Bolt " + Name + " , head size + " + HeadSize + ", length "+ Length;
          }
      }
      
      public interface IWasher : IWidget { }
      public class Washer : IWasher
      {
          public string Name { get; set; }
          public int Thickness { get; set; }
      
          public string ToString(string indent = "")
          {
              return indent+ "Washer "+ Name + ", thickness " + Thickness;
          }
      }
      
      public interface INut : IWidget { }
      public class Nut : INut
      {
          public string Name { get; set; }
          public int Size { get; set; }
      
          public string ToString(string indent = "")
          {
              return indent + "Nut " +Name + ", size "  + Size;
          }
      }
      

      更改:

      • 我为每个类添加了特定的接口,这些接口都共享List<IWidget> Assembly.Parts中使用的公共接口IWidget.
      • I added specific interfaces for each class which all share the common interface, IWidget, used in List<IWidget> Assembly.Parts.

      输出:

      ----- Serializing widget tree to JSON -----
      Created a contract for 'Assembly'.
      Binding type Assembly to alias A
      Created a contract for 'IWidget'.
      Binding type Assembly to alias A
      Created a contract for 'Bolt'.
      Binding type Bolt to alias B
      Created a contract for 'Washer'.
      Binding type Washer to alias W
      Created a contract for 'Nut'.
      Binding type Nut to alias N
      Binding type Assembly to alias A
      Binding type Bolt to alias B
      Binding type Washer to alias W
      Binding type Washer to alias W
      Binding type Nut to alias N
      {
        "$type": "A",
        "Name": "Gizmo",
        "Parts": [
          {
            "$type": "A",
            "Name": "Upper Doodad",
            "Parts": [
              {
                "$type": "B",
                "Name": "UB1",
                "HeadSize": 2,
                "Length": 6
              },
              {
                "$type": "W",
                "Name": "UW1",
                "Thickness": 1
              },
              {
                "$type": "N",
                "Name": "UN1",
                "Size": 2
              }
            ]
          },
          {
            "$type": "A",
            "Name": "Lower Doodad",
            "Parts": [
              {
                "$type": "B",
                "Name": "LB1",
                "HeadSize": 3,
                "Length": 5
              },
              {
                "$type": "W",
                "Name": "LW1",
                "Thickness": 2
              },
              {
                "$type": "W",
                "Name": "LW2",
                "Thickness": 1
              },
              {
                "$type": "N",
                "Name": "LN1",
                "Size": 3
              }
            ]
          }
        ]
      }
      
      ----- Deserializing JSON back to widgets -----
      Created a contract for 'IAssembly'.
      Binding alias A to type IAssembly
      Created a 'IAssembly' instance.
      Run-time exception (line 179): Object reference not set to an instance of an object.
      
      Stack Trace:
      
      [System.NullReferenceException: Object reference not set to an instance of an object.]
         at Assembly.ToString(String indent) :line 179
         at Program.Main() :line 68
      

      串行化似乎是完全正确的,但是异常和控制台输出表明,虽然"root" Assembly实例创建正确,没有填充其成员.

      Serialization appears to be fully correct, but the exception and console outputs indicate that while the "root" Assembly instance was created correctly, its members were not populated.

      正如我在上一个问题中提到的那样,我目前正在将JsonConverterSerializationBinder结合使用-除性能外,它都能正常工作(请参见上面的链接).自从我实施该解决方案以来已经有一段时间了,我记得我发现很难将自己的头缠在预期或实际的数据流上,尤其是.同时反序列化.而且,同样,我还不太了解ContractResolver在这里如何发挥作用.

      As mentioned in my previous question, I'm currently using a JsonConverter in combination with a SerializationBinder - and it works fine, except for the performance (see link above). It's been a while since I implemented that solution and I remember finding it hard to wrap my head around the intended or actual data flow, esp. while deserializing. And, likewise, I don't yet quite understand how the ContractResolver comes into play here.

      我想念什么?

      推荐答案

      这非常接近工作.问题在于您已将IWidget派生接口设置为空.由于SerializationBinder将JSON中的$type代码绑定到接口而不是具体类型,因此合同解析器将查看这些接口上的属性以确定可以反序列化的内容. (合同解析器的主要职责是将JSON中的属性映射到类结构中它们各自的属性.它为每种类型分配一个合同",从而控制在反序列化期间如何处理该类型:如何创建,是否对其应用JsonConverter,依此类推.)

      This is very close to working. The problem is that you have made your IWidget derivative interfaces empty. Since the SerializationBinder is binding the $type codes in the JSON to interfaces instead of concrete types, the contract resolver is going to look at the properties on those interfaces to determine what can be deserialized. (The contract resolver's main responsibility is to map properties in the JSON to their respective properties in the class structure. It assigns a "contract" to each type, which governs how that type is handled during de/serialization: how it is created, whether to apply a JsonConverter to it, and so forth.)

      IAssembly上没有Parts列表,因此就可以进行反序列化.另外,IWidgetName属性没有设置器,因此也不会填充根汇编对象的名称.在演示代码的ToString()方法中抛出了NRE,该代码试图在没有适当的空检查(就在我身上)的情况下转储空的Parts列表.无论如何,如果将公共Widget属性从具体的Widget类型添加到它们各自的接口中,那么它将起作用.所以:

      IAssembly doesn't have a Parts list on it, so that's as far as the deserialization gets. Also, the Name property on IWidget does not have a setter, so the name of the root assembly object doesn't get populated either. The NRE is being thrown in the ToString() method in the demo code which is trying to dump out the null Parts list without an appropriate null check (that one's on me). Anyway, if you add the public properties from the concrete Widget types to their respective interfaces, then it works. So:

      public interface IWidget
      {
          string Name { get; set; }
          string ToString(string indent = "");
      }
      
      public interface IAssembly : IWidget 
      {
          List<IWidget> Parts { get; set; }
      }
      
      public interface IBolt : IWidget 
      {
          int HeadSize { get; set; }
          int Length { get; set; }
      }
      
      public interface IWasher : IWidget 
      {
          public int Thickness { get; set; }
      }
      
      public interface INut : IWidget 
      {
          public int Size { get; set; }
      }
      

      这是工作中的小提琴: https://dotnetfiddle.net/oJ54VD

      Here's the working Fiddle: https://dotnetfiddle.net/oJ54VD

      这篇关于使用Newtonsoft.Json,如何将SerializationBinder和CustomResolver一起反序列化抽象/接口类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆