2012-06-19 36 views
28

Giả sử tôi có hệ thống phân cấp lớp sau đây:Sử dụng một kiểu tùy chỉnh phân biệt nói JSON.net loại của một hệ thống phân cấp lớp để deserialize

public abstract class Organization 
{ 
    /* properties related to all organizations */ 
} 

public sealed class Company : Organization 
{ 
    /* properties related to companies */ 
} 

public sealed class NonProfitOrganization : Organization 
{ 
    /* properties related to non profit organizations */ 
} 

Có thể có json.net sở hữu sử dụng (nói "loại" hoặc "phân biệt đối xử") để xác định loại đối tượng khi nó deserializes tổ chức? Ví dụ, sau đây nên deserialize một trường hợp của Công ty.

{ 
    "type": "company" 
    /* other properties related to companies */ 
} 

Và những điều sau đây cần hủy bỏ một phiên bản của NonProfitOrganization.

{ 
    "type": "non-profit" 
    /* other properties related to non profit */ 
} 

Khi tôi gọi như sau:

Organization organization = JsonConvert.DeserializeObject<Organization>(payload); 

nơi tải trọng là các đoạn JSON trên. Tôi đã xem xét thiết lập "TypeNameHandling" trên thuộc tính hoặc các lớp nhưng nó tuần tự hóa toàn bộ loại .NET, không phải là "di động" giữa máy khách và máy chủ khi các lớp được định nghĩa trong các không gian tên và các cụm khác nhau.

Tôi muốn xác định loại là cách trung lập mà khách hàng viết bằng bất kỳ ngôn ngữ nào có thể sử dụng để xác định loại thực tế của loại đối tượng đang được đăng.

Trả lời

19

Trong trường hợp bạn vẫn đang tìm kiếm, đây là một ví dụ: http://james.newtonking.com/archive/2011/11/19/json-net-4-0-release-4-bug-fixes.aspx

Điều này sẽ cho phép bạn tạo một bảng dựa mapping:

public class TypeNameSerializationBinder : SerializationBinder 
{ 
    public TypeNameSerializationBinder(Dictionary<Type, string> typeNames = null) 
    { 
     if (typeNames != null) 
     { 
      foreach (var typeName in typeNames) 
      { 
       Map(typeName.Key, typeName.Value); 
      } 
     } 
    } 

    readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>(); 
    readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); 

    public void Map(Type type, string name) 
    { 
     this.typeToName.Add(type, name); 
     this.nameToType.Add(name, type); 
    } 

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName) 
    { 
     var name = typeToName.Get(serializedType); 
     if (name != null) 
     { 
      assemblyName = null; 
      typeName = name; 
     } 
     else 
     { 
      assemblyName = serializedType.Assembly.FullName; 
      typeName = serializedType.FullName;     
     } 
    } 

    public override Type BindToType(string assemblyName, string typeName) 
    { 
     if (assemblyName == null) 
     { 
      var type = this.nameToType.Get(typeName); 
      if (type != null) 
      { 
       return type; 
      } 
     } 
     return Type.GetType(string.Format("{0}, {1}", typeName, assemblyName), true); 
    } 
} 

Mã này có một khiếm khuyết nhỏ trong đó nếu một loại Việc ánh xạ tên được cố định ở nơi kiểu duy nhất nhưng tên đã được sử dụng, phương thức Map sẽ ném một ngoại lệ sau khi ánh xạ kiểu-to-name đã được thêm vào để lại bảng trong trạng thái không nhất quán.

+4

Tôi không biết gì tình hình là vào năm 2012, nhưng tôi nghĩ rằng bây giờ tốt hơn là kế thừa từ DefaultSerializationBinder và quay trở lại triển khai cơ sở, vì sau đó bạn có thể hưởng lợi từ việc triển khai chính xác của nó. –

8

Để nhận thêm câu trả lời của eulerfx; Tôi muốn áp dụng thuộc tính DisplayName cho một lớp và tự động trở thành tên loại được sử dụng; cho mục đích đó:

public class DisplayNameSerializationBinder : DefaultSerializationBinder 
{ 
    private Dictionary<string, Type> _nameToType; 
    private Dictionary<Type, string> _typeToName; 

    public DisplayNameSerializationBinder() 
    { 
     var customDisplayNameTypes = 
      this.GetType() 
       .Assembly 
       //concat with references if desired 
       .GetTypes() 
       .Where(x => x 
        .GetCustomAttributes(false) 
        .Any(y => y is DisplayNameAttribute)); 

     _nameToType = customDisplayNameTypes.ToDictionary(
      t => t.GetCustomAttributes(false).OfType<DisplayNameAttribute>().First().DisplayName, 
      t => t); 

     _typeToName = _nameToType.ToDictionary(
      t => t.Value, 
      t => t.Key); 

    } 

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName) 
    { 
     if (false == _typeToName.ContainsKey(serializedType)) 
     { 
      base.BindToName(serializedType, out assemblyName, out typeName); 
      return; 
     } 

     var name = _typeToName[serializedType]; 

     assemblyName = null; 
     typeName = name; 
    } 

    public override Type BindToType(string assemblyName, string typeName) 
    { 
     if (_nameToType.ContainsKey(typeName)) 
      return _nameToType[typeName]; 

     return base.BindToType(assemblyName, typeName); 
    } 
} 

và sử dụng Ví dụ:

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

[DisplayName("bool")] 
public class BooleanParameter : Parameter 
{ 
} 

[DisplayName("string")] 
public class StringParameter : Parameter 
{ 
    public int MinLength { get; set; } 
    public int MaxLength { get; set; } 
} 

[DisplayName("number")] 
public class NumberParameter : Parameter 
{ 
    public double Min { get; set; } 
    public double Max { get; set; } 
    public string Unit { get; set; } 
} 

[DisplayName("enum")] 
public class EnumParameter : Parameter 
{ 
    public string[] Values { get; set; } 
} 

internal class Program 
{ 
    private static void Main(string[] args) 
    { 
     var parameters = new Parameter[] 
     { 
      new BooleanParameter() {Name = "alive"}, 
      new StringParameter() {Name = "name", MinLength = 0, MaxLength = 10}, 
      new NumberParameter() {Name = "age", Min = 0, Max = 120}, 
      new EnumParameter() {Name = "status", Values = new[] {"Single", "Married"}} 
     }; 

     JsonConvert.DefaultSettings =() => new JsonSerializerSettings 
     { 
      Binder = new DisplayNameSerializationBinder(), 
      TypeNameHandling = TypeNameHandling.Auto, 
      NullValueHandling = NullValueHandling.Ignore, 
      DefaultValueHandling = DefaultValueHandling.Ignore, 
      Formatting = Formatting.Indented, 
      ContractResolver = new CamelCasePropertyNamesContractResolver() 
     }; 

     var json = JsonConvert.SerializeObject(parameters); 
     var loadedParams = JsonConvert.DeserializeObject<Parameter[]>(json); 
     Console.WriteLine(JsonConvert.SerializeObject(loadedParams)); 


    } 
} 

đầu ra:

[ 
    { 
    "$type": "bool", 
    "name": "alive" 
    }, 
    { 
    "$type": "string", 
    "maxLength": 10, 
    "name": "name" 
    }, 
    { 
    "$type": "number", 
    "max": 120.0, 
    "name": "age" 
    }, 
    { 
    "$type": "enum", 
    "values": [ 
     "Single", 
     "Married" 
    ], 
    "name": "status" 
    } 
] 
5

Tôi đã viết giải pháp hoàn toàn tường thuật với khả năng xác định lĩnh vực phân biệt tùy chỉnh, và cung cấp tên scoped xử lý cho mỗi lớp cơ sở (trái ngược với các JsonSerializationSettings toàn cầu, đặc biệt là trên các Web-Api khác nhau khi chúng ta không có khả năng chỉ định các tùy chỉnh JsonSerializationSettings).

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

// Discriminated Json Converter (JsonSubtypes) implementation for .NET 
// 
// MIT License 
// 
// Copyright (c) 2016 Anatoly Ressin 

// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions: 
// 
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software. 
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE. 

////////////////////// USAGE //////////////////////////////////////////////////////////////////////////////// 


[JsonConverter(typeof(JsonSubtypes))]  // Discriminated base class SHOULD NOT be abstract 
public class ShapeBase { 
    [JsonTag, JsonProperty("@type")]  // it SHOULD contain a property marked with [JsonTag] 
    public string Type {get;set;}   // only one [JsonTag] annotation allowed per discriminated class 

              // it COULD contain other properties, however this is NOT RECOMMENDED 
              // Rationale: instances of this class will be created at deserialization 
              // only for tag sniffing, and then thrown away. 
} 

public abstract class Shape: ShapeBase { // If you want abstract parent - extend the root 
    public abstract double GetArea();  // with needed abstract stuff, then use this class everywhere (see DEMO below) 
} 

[JsonSubtype("circle")]     // Every final class-case SHOULD be marked with [JsonSubtype(tagValue)] 
public class Circle: Shape {    // Two disctinct variant classes MUST have distinct tagValues 

    [JsonProperty("super-radius")]  // You CAN use any Json-related annotation as well 
    public double Radius { get; set; }  
    public override double GetArea() { 
     return Radius * Radius * Math.PI; 
    } 
}           

[JsonSubtype("rectangle")] 
public class Rectangle: Shape { 
    public double Height { get; set; } 
    public double Width { get; set; } 
    public override double GetArea() { 
     return Width * Height; 
    } 
} 

[JsonSubtype("group")] 
public class Group: Shape { 
    [JsonProperty("shapes")] 
    public List<Shape> Items { get; set; } 
    public override double GetArea() { 
     return Items.Select(item => item.GetArea()).Sum(); 
    } 
} 


              // Every final class-case SHOULD be registered with JsonSubtypes.register(typeof(YourConcreteClass)) 
              // either manually or with auto-register capability: 
              // You can auto-register all classes marked with [JsonSubtype(tag)] in given Assembly 
              // using JsonSubtypes.autoRegister(yourAssembly) 



////////////////// DEMO ///////////////////////////////////////////////////////////////////////////////// 



public class Program 
{ 
    public static void Main() 
    { 
     JsonSubtypes.autoRegister(Assembly.GetExecutingAssembly()); 
     Shape original = new Group() { 
      Items = new List<Shape> { 
       new Circle() { Radius = 5 }, 
       new Rectangle() { Height = 10, Width = 20 } 
      } 
     }; 
     string str = JsonConvert.SerializeObject(original); 
     Console.WriteLine(str); 
     var copy = JsonConvert.DeserializeObject(str,typeof(Shape)) as Shape; 

     // Note: we can deserialize object using any class from the hierarchy. 
     // Under the hood, anyway, it will be deserialized using the top-most 
     // base class annotated with [JsonConverter(typeof(JsonSubtypes))]. 
     // Thus, only soft-casts ("as"-style) are safe here. 

     Console.WriteLine("original.area = {0}, copy.area = {1}", original.GetArea(), copy.GetArea()); 


    } 
} 



//////////////////////// IMPLEMENTATION ////////////////////////////////////////////////////////////////// 



public class JsonSubtypeClashException: Exception { 

    public string TagValue { get; private set;} 
    public Type RootType { get; private set; } 
    public Type OldType { get; private set; } 
    public Type NewType { get; private set; } 

    public JsonSubtypeClashException(Type rootType, string tagValue, Type oldType, Type newType): base(
     String.Format(
      "JsonSubtype Clash for {0}[tag={1}]: oldType = {2}, newType = {3}", 
      rootType.FullName, 
      tagValue, 
      oldType.FullName, 
      newType.FullName 
     ) 
    ) { 
     TagValue = tagValue; 
     RootType = rootType; 
     OldType = oldType; 
     NewType = newType; 
    } 
} 

public class JsonSubtypeNoRootException: Exception { 
    public Type SubType { get; private set; } 

    public JsonSubtypeNoRootException(Type subType): base(
     String.Format(
      "{0} should be inherited from the class with the [JsonConverter(typeof(JsonSubtypes))] attribute", 
      subType.FullName 
     ) 
    ) { 
     SubType = subType; 
    } 

} 

public class JsonSubtypeNoTagException: Exception { 
    public Type SubType { get; private set; } 

    public JsonSubtypeNoTagException(Type subType): base(
     String.Format(
      @"{0} should have [JsonSubtype(""..."")] attribute", 
      subType.FullName 
     ) 
    ) { 
     SubType = subType; 
    } 

} 

public class JsonSubtypeNotRegisteredException: Exception { 
    public Type Root { get; private set; } 
    public string TagValue { get; private set; } 
    public JsonSubtypeNotRegisteredException(Type root, string tagValue): base(
     String.Format(
      @"Unknown tag={1} for class {0}", 
      root.FullName, 
      tagValue 
     ) 
    ) { 
     Root = root; 
     TagValue = tagValue; 
    } 
} 


[AttributeUsage(AttributeTargets.Class)] 
public class JsonSubtypeAttribute: Attribute { 
    private string tagValue; 
    public JsonSubtypeAttribute(string tagValue) { 
     this.tagValue = tagValue; 
    } 
    public string TagValue { 
     get { 
      return tagValue; 
     } 
    } 

} 


public static class JsonSubtypesExtension { 

    public static bool TryGetAttribute<T>(this Type t, out T attribute) where T: Attribute { 
     attribute = t.GetCustomAttributes(typeof(T), false).Cast<T>().FirstOrDefault(); 
     return attribute != null; 
    } 

    private static Dictionary<Type, PropertyInfo> tagProperties = new Dictionary<Type, PropertyInfo>(); 

    public static bool TryGetTagProperty(this Type t, out PropertyInfo tagProperty) { 
     if (!tagProperties.TryGetValue(t, out tagProperty)) { 
      JsonConverterAttribute conv; 
      if (t.TryGetAttribute(out conv) && conv.ConverterType == typeof(JsonSubtypes)) { 
       var props = (from prop in t.GetProperties() where prop.GetCustomAttribute(typeof(JsonTagAttribute)) != null select prop).ToArray(); 
       if (props.Length == 0) throw new Exception("No tag"); 
       if (props.Length > 1) throw new Exception("Multiple tags"); 
       tagProperty = props[0]; 
      } else { 
       tagProperty = null; 
      } 
      tagProperties[t] = tagProperty; 

     } 
     return tagProperty != null; 
    } 

    public static bool TryGetTagValue(this Type t, out string tagValue) { 
     JsonSubtypeAttribute subtype; 
     if (t.TryGetAttribute(out subtype)) { 
      tagValue = subtype.TagValue; 
      return true; 
     } else { 
      tagValue = null; 
      return false; 
     } 
    } 

    public static bool TryGetJsonRoot(this Type t, out Type root, out PropertyInfo tagProperty) { 
     root = t; 
     do { 
      if (root.TryGetTagProperty(out tagProperty)) { 
       return true; 
      } 
      root = root.BaseType; 
     } while (t != null); 
     return false; 
    } 
} 


public class JsonTagAttribute: Attribute { 
} 

public class JsonTagInfo { 
    public PropertyInfo Property { get; set; } 
    public string Value { get; set; } 
} 

public class JsonRootInfo { 
    public PropertyInfo Property { get; set; } 
    public Type Root { get; set; } 
} 


public abstract class DefaultJsonConverter: JsonConverter { 

    [ThreadStatic] 
    private static bool silentWrite; 

    [ThreadStatic] 
    private static bool silentRead; 

    public sealed override bool CanWrite { 
     get { 
      var canWrite = !silentWrite; 
      silentWrite = false; 
      return canWrite; 
     } 
    } 

    public sealed override bool CanRead { 
     get { 
      var canRead = !silentRead; 
      silentRead = false; 
      return canRead; 
     } 
    } 

    protected void _WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { 
     silentWrite = true; 
     serializer.Serialize(writer, value); 
    } 

    protected Object _ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) {  
     silentRead = true; 
     return serializer.Deserialize(reader, objectType); 
    } 

} 



public class JsonSubtypes: DefaultJsonConverter { 

    private static Dictionary<Type, Dictionary<string, Type>> implementations = new Dictionary<Type, Dictionary<string, Type>>(); 
    private static Dictionary<Type, JsonTagInfo> tags = new Dictionary<Type, JsonTagInfo>();  
    private static Dictionary<Type, JsonRootInfo> roots = new Dictionary<Type, JsonRootInfo>(); 


    public static void register(Type newType) { 
     PropertyInfo tagProperty; 
     Type root; 
     if (newType.TryGetJsonRoot(out root, out tagProperty)) { 
      for(var t = newType; t != root; t = t.BaseType) { 
       roots[t] = new JsonRootInfo() { 
        Property = tagProperty, 
        Root = root 
       }; 
      } 
      roots[root] = new JsonRootInfo() { 
        Property = tagProperty, 
        Root = root 
      }; 
      Dictionary<string, Type> implementationMap; 
      if (!implementations.TryGetValue(root, out implementationMap)) { 
       implementationMap = new Dictionary<string, Type>(); 
       implementations[root] = implementationMap; 
      } 
      JsonSubtypeAttribute attr; 
      if (!newType.TryGetAttribute(out attr)) { 
       throw new JsonSubtypeNoTagException(newType); 
      } 
      var tagValue = attr.TagValue; 
      Type oldType; 
      if (implementationMap.TryGetValue(tagValue, out oldType)) { 
       throw new JsonSubtypeClashException(root, tagValue, oldType, newType); 
      } 
      implementationMap[tagValue] = newType; 
      tags[newType] = new JsonTagInfo() { 
       Property = tagProperty, 
       Value = tagValue 
      }; 

     } else { 
      throw new JsonSubtypeNoRootException(newType); 
     } 
    } 

    public static void autoRegister(Assembly assembly) { 
     foreach(var type in assembly.GetTypes().Where(type => type.GetCustomAttribute<JsonSubtypeAttribute>() != null)) { 
      register(type); 
     }  
    } 


    public override bool CanConvert(Type t) { 
     return true; 
    } 

    public static T EnsureTag<T>(T value) { 
     JsonTagInfo tagInfo; 
     if (tags.TryGetValue(value.GetType(), out tagInfo)) { 
      tagInfo.Property.SetValue(value, tagInfo.Value); 
     } 
     return value; 
    } 

    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) { 
     _WriteJson(writer, EnsureTag(value), serializer); 
    } 

    public override Object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) { 
     JsonTagInfo tagInfo; 
     if (tags.TryGetValue(objectType, out tagInfo)) { 
      return _ReadJson(reader, objectType, existingValue, serializer); 
     } else { 
      JsonRootInfo rootInfo; 
      if (roots.TryGetValue(objectType, out rootInfo)) { 
       JToken t = JToken.ReadFrom(reader); 
       var stub = _ReadJson(t.CreateReader(), rootInfo.Root, existingValue, serializer); 
       var tagValue = rootInfo.Property.GetValue(stub) as string; 
       var implementationMap = implementations[rootInfo.Root]; 
       Type implementation; 
       if (implementationMap.TryGetValue(tagValue, out implementation)) { 
        return ReadJson(t.CreateReader(), implementation, null, serializer); 
       } else { 
        throw new JsonSubtypeNotRegisteredException(rootInfo.Root, tagValue); 
       } 
      } else { 
       return _ReadJson(reader, objectType, existingValue, serializer); 
      } 
     } 
    } 

    public static T Deserialize<T>(string s) where T: class { 
     return JsonConvert.DeserializeObject(s, typeof(T)) as T; 
    } 

    public static string Serialize<T>(T value) where T: class { 
     return JsonConvert.SerializeObject(value); 
    } 



} 

đầu ra:

{"shapes":[{"super-radius":5.0,"@type":"circle"},{"Height":10.0,"Width":20.0,"@type":"rectangle"}],"@type":"group"} 
original.area = 278.539816339745, copy.area = 278.539816339745 

Bạn có thể lấy nó ở đây:

https://dotnetfiddle.net/ELcvnk

+0

Thu hút những đóng góp to lớn! Có anyway để có được các "@ loại" tham số xuất hiện đầu tiên trong json? –

+1

@ HenrikHolmgaardHøyer trong lớp cơ sở của bạn, thêm một 'Order' vào JsonProperty của bạn. '[JsonTag, JsonProperty (" @ type ", Order = Int32.MinValue)]' 'chuỗi công khai Loại {get; bộ; } ' –

2

Với khác thực hiện JsonSubtypes chuyển đổi.

Cách sử dụng:

[JsonConverter(typeof(JsonSubtypes), "Sound")] 
    [JsonSubtypes.KnownSubType(typeof(Dog), "Bark")] 
    [JsonSubtypes.KnownSubType(typeof(Cat), "Meow")] 
    public class Annimal 
    { 
     public virtual string Sound { get; } 
     public string Color { get; set; } 
    } 

    public class Dog : Annimal 
    { 
     public override string Sound { get; } = "Bark"; 
     public string Breed { get; set; } 
    } 

    public class Cat : Annimal 
    { 
     public override string Sound { get; } = "Meow"; 
     public bool Declawed { get; set; } 
    } 

    [TestMethod] 
    public void Demo() 
    { 
     var annimal = JsonConvert.DeserializeObject<Annimal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}"); 
     Assert.AreEqual("Jack Russell Terrier", (annimal as Dog)?.Breed); 
    } 

việc thực hiện chuyển đổi có thể được tải trực tiếp từ kho lưu trữ: JsonSubtypes.cs và cũng là availble như một nuget package

Các vấn đề liên quan