44

次のクラス階層があるとします。

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」または「discriminator」など)を使用して、組織を逆シリアル化するときにオブジェクトのタイプを判別することはできますかたとえば、以下はCompanyのインスタンスを逆シリアル化する必要があります。

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

そして、以下はNonProfitOrganizationのインスタンスを逆シリアル化する必要があります。

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

私が次のように呼ぶとき:

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

ここで、ペイロードは上記のJSONスニペットです。プロパティまたはクラスに「 TypeNameHandling 」を設定することを検討しましたが、クラスが異なる名前空間とアセンブリで定義されている場合、クライアントとサーバー間で「移植可能」ではない.NETタイプ全体がシリアル化されます。

タイプは、任意の言語で記述されたクライアントがシリアル化されるオブジェクトタイプの実際のタイプを判別するために使用できる中立的な方法であると定義したいと思います。

4

5 に答える 5

22

あなたがまだ探している場合は、ここに例があります:http: //james.newtonking.com/archive/2011/11/19/json-net-4-0-release-4-bug-fixes.aspx

これにより、テーブルベースのマッピングを作成できます。

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);
    }
}

コードには、タイプが一意であるが名前がすでに使用されているタイプ名マッピングが試行された場合、タイプから名前へのマッピングがすでに追加された後に例外がスローされ、テーブルが一貫性のない状態。

于 2012-08-30T18:55:45.207 に答える
12

eulerfxの答えをさらに進めるために; DisplayNameクラスに属性を適用し、それが自動的に使用される型名になるようにしたかったのです。そのために:

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);
    }
}

および使用例:

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));


    }
}

出力:

[
  {
    "$type": "bool",
    "name": "alive"
  },
  {
    "$type": "string",
    "maxLength": 10,
    "name": "name"
  },
  {
    "$type": "number",
    "max": 120.0,
    "name": "age"
  },
  {
    "$type": "enum",
    "values": [
      "Single",
      "Married"
    ],
    "name": "status"
  }
]
于 2015-11-26T15:17:17.743 に答える
10

カスタム識別子フィールドを指定し、基本クラスごとにスコープ名を処理する機能を備えた純粋な宣言型ソリューションを作成しました(特に、カスタムJsonSerializationSettingsを指定する機能がない場合の異なるWeb-Apiでは、usecureグローバル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);
    }



}

出力:

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

于 2016-03-22T06:31:48.103 に答える
7

別のJsonSubtypesコンバーターの実装。

使用法:

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

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

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

[TestMethod]
public void Demo()
{
    var input = @"{""Sound"":""Bark"",""Breed"":""Jack Russell Terrier""}"
    var animal = JsonConvert.DeserializeObject<Animal>(input);

    Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
}

コンバーターの実装は、リポジトリから直接ダウンロードできます。また、 nugetパッケージJsonSubtypes.csとしても利用できます。

于 2017-06-19T22:08:19.683 に答える
2

このJsonKnownTypesを使用します。これは、使用方法と非常によく似ており、いくつかの属性を追加します。

[JsonConverter(typeof(JsonKnownTypeConverter<Organization>))]
[JsonDiscriminator(Name = "discriminator")]
[JsonKnownType(typeof(Company), "company")]
[JsonKnownType(typeof(NonProfitOrganization), "non-profit")]
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 */
}

そしてシリアル化:

var json = JsonConvert.SerializeObject(youObject)

出力json:

{..., "discriminator":"non-profit"} //if object was NonProfitOrganization

デシリアライズ:

var organization = JsonConvert.DeserializeObject<Organization>(payload);
于 2020-02-19T09:41:29.087 に答える