Использование специального дискриминатора типа, чтобы сообщить JSON.net, какой тип иерархии classов десериализуем

Предположим, что у меня есть следующая иерархия classов:

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 */ } 

Возможно ли использовать свойство json.net (например, «тип» или «дискриминатор»), чтобы определить, какой тип объекта будет использоваться при десериализации организации? Например, следующее должно десериализовать экземпляр Компании.

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

И следующее должно десериализовать экземпляр NonProfitOrganization.

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

Когда я вызываю следующее:

 Organization organization = JsonConvert.DeserializeObject(payload); 

где полезная нагрузка – это приведенные выше fragmentы JSON. Я взглянул на установку « TypeNameHandling » на свойствах или classах, но он сериализует весь тип .NET, который не является «переносимым» между клиентом и сервером, когда classы определены в разных пространствах имен и assemblyх.

Я бы предпочел определить тип нейтральной формы, которую клиенты, написанные на любом языке, могут использовать для определения фактического типа типа объекта, который сериализуется.

Если вы все еще ищете, вот пример: http://james.newtonking.com/archive/2011/11/19/json-net-4-0-release-4-bug-fixes.aspx

Это позволит вам создать табличное сопоставление:

 public class TypeNameSerializationBinder : SerializationBinder { public TypeNameSerializationBinder(Dictionary typeNames = null) { if (typeNames != null) { foreach (var typeName in typeNames) { Map(typeName.Key, typeName.Value); } } } readonly Dictionary typeToName = new Dictionary(); readonly Dictionary nameToType = new Dictionary(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); } } 

Код имеет небольшой дефект в том случае, если при сопоставлении типов имен, где тип является уникальным, но имя уже используется, метод Map генерирует исключение после того, как уже добавлено сопоставление типа к имени, оставив таблицу в несогласованное состояние.

Чтобы получить ответ eulerfx дальше; Я хотел применить атрибут DisplayName к classу и автоматически использовать имя типа; с этой целью:

 public class DisplayNameSerializationBinder : DefaultSerializationBinder { private Dictionary _nameToType; private Dictionary _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().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); } } 

и пример использования:

 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(json); Console.WriteLine(JsonConvert.SerializeObject(loadedParams)); } } 

вывод:

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

Я написал чисто декларативное решение с возможностью указать поле пользовательского дискриминатора и обеспечил обработку именных областей в базовом classе (в отличие от usecure global JsonSerializationSettings, особенно в разных Web-Api, когда у нас нет возможности указывать пользовательские настройки 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 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 { 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(this Type t, out T attribute) where T: Attribute { attribute = t.GetCustomAttributes(typeof(T), false).Cast().FirstOrDefault(); return attribute != null; } private static Dictionary tagProperties = new Dictionary(); 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> implementations = new Dictionary>(); private static Dictionary tags = new Dictionary(); private static Dictionary roots = new Dictionary(); 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 implementationMap; if (!implementations.TryGetValue(root, out implementationMap)) { implementationMap = new Dictionary(); 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() != null)) { register(type); } } public override bool CanConvert(Type t) { return true; } public static T EnsureTag(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(string s) where T: class { return JsonConvert.DeserializeObject(s, typeof(T)) as T; } public static string Serialize(T value) where T: class { return JsonConvert.SerializeObject(value); } } 

вывод:

 {"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 

Вы можете взять его здесь:

https://dotnetfiddle.net/ELcvnk

С другой версией конвертера JsonSubtypes .

Применение:

  [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("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}"); Assert.AreEqual("Jack Russell Terrier", (annimal as Dog)?.Breed); } 

реализация конвертера может быть непосредственно загружена из репозитория: JsonSubtypes.cs и также доступна как пакет nuget

  • Почему (int) (объект) 10m бросает исключение «Указанный приведение недействителен»?
  • В чем разница между WCF Web API и ASP.NET Web API
  • Преобразование изображения в оттенки серого
  • Запрос LINQ для возврата словаря
  • Как создать пробную версию программного обеспечения .NET?
  • Недвижимость против ivar во времена ARC
  • друг И встроенный метод, в чем смысл?
  • Как разрешить выход ASMX-файла JSON
  • Я пытаюсь понять getchar ()! = EOF
  • Дезаминирование гетерогенного массива JSON в ковариантный список с использованием JSON.NET
  • Форматирование текста в TextBlock
  • Interesting Posts

    Проверка подлинности электронной почты с использованием регулярного выражения в JSF 2 / PrimeFaces

    Изменение флажка jQuery и событие click

    Как преобразовать BitArray в один int?

    Как отличать сеансы в браузерах?

    Каков наилучший способ отменить распространение событий между вложенными вызовами ng-click?

    объединение двух png-файлов в android

    Могу ли я держать Google от кражи моего курсора в Firefox?

    У меня есть PCI-карта с напряжением 3,3 вольта, работающая на 5-вольтном PCI-слоте. Как это возможно?

    Добавление machineKey в web.config на веб-сайтах

    Почему некоторые встроенные функции Python имеют пропуск?

    Поддерживается ли GTX 960 в Intel Chipset Q75?

    Остановить cron от отправки по электронной почте мне

    Есть ли разница в производительности между циклами for и циклом for-each?

    Как использовать событие DataReceived объекта SerialPort Port в C #?

    Как распечатать мой объект Java, не получив «SomeType @ 2f92e0f4»?

    Давайте будем гением компьютера.