Temel sınıf nesnelerinin listesinin serisini kaldırmak için JSON.NET'te özel JsonConverter nasıl uygulanır?


301

Burada verilen JSON.net örneğini genişletmeye çalışıyorum http://james.newtonking.com/projects/json/help/CustomCreationConverter.html

Temel sınıf / arabirim türetilmiş başka bir alt sınıf var

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Employee : Person
{
    public string Department { get; set; }
    public string JobTitle { get; set; }
}

public class Artist : Person
{
    public string Skill { get; set; }
}

List<Person> people  = new List<Person>
{
    new Employee(),
    new Employee(),
    new Artist(),
};

Json'u takip ederek <Kişi> Listesine geri dön

[
  {
    "Department": "Department1",
    "JobTitle": "JobTitle1",
    "FirstName": "FirstName1",
    "LastName": "LastName1"
  },
  {
    "Department": "Department2",
    "JobTitle": "JobTitle2",
    "FirstName": "FirstName2",
    "LastName": "LastName2"
  },
  {
    "Skill": "Painter",
    "FirstName": "FirstName3",
    "LastName": "LastName3"
  }
]

TypeNameHandling JsonSerializerSettings kullanmak istemiyorum. Özellikle bu işlemek için özel JsonConverter uygulaması arıyorum. Bunun etrafındaki belgeler ve örnekler internette oldukça seyrek. JsonConverter geçersiz kılınmış ReadJson () yöntemi uygulaması elde edemiyor gibi görünüyor.


Yanıtlar:


315

Standardı kullanarak, CustomCreationConverterdoğru türün ( Personveya Employee) nasıl oluşturulacağı konusunda uğraşıyordum , çünkü bunu belirlemek için JSON'u analiz etmeniz gerekiyor ve bunu kullanarak bunu yapmak için yerleşik bir yol yok.Create yöntemi yöntem yok.

Tür dönüşüm ile ilgili bir tartışma iş parçacığı buldum ve cevap verdi. İşte bir bağlantı: Tür dönüştürme .

Gerekli olan alt sınıfı JsonConverter, ReadJsonyöntemi geçersiz kılmak ve Createkabul eden yeni bir soyut yöntem oluşturmaktır JObject.

JObject sınıfı, bir JSON nesnesini yüklemek için bir yol sağlar ve bu nesne içindeki verilere erişim sağlar.

Geçersiz kılınan ReadJsonyöntem bir oluşturur JObjectve Create(türetilmiş dönüştürücü sınıfımız tarafından uygulanan) yöntemi çağırır .JObject örneği .

Bu JObjectörnek daha sonra belirli alanların varlığını kontrol ederek doğru türü belirlemek için analiz edilebilir.

Misal

string json = "[{
        \"Department\": \"Department1\",
        \"JobTitle\": \"JobTitle1\",
        \"FirstName\": \"FirstName1\",
        \"LastName\": \"LastName1\"
    },{
        \"Department\": \"Department2\",
        \"JobTitle\": \"JobTitle2\",
        \"FirstName\": \"FirstName2\",
        \"LastName\": \"LastName2\"
    },
        {\"Skill\": \"Painter\",
        \"FirstName\": \"FirstName3\",
        \"LastName\": \"LastName3\"
    }]";

List<Person> persons = 
    JsonConvert.DeserializeObject<List<Person>>(json, new PersonConverter());

...

public class PersonConverter : JsonCreationConverter<Person>
{
    protected override Person Create(Type objectType, JObject jObject)
    {
        if (FieldExists("Skill", jObject))
        {
            return new Artist();
        }
        else if (FieldExists("Department", jObject))
        {
            return new Employee();
        }
        else
        {
            return new Person();
        }
    }

    private bool FieldExists(string fieldName, JObject jObject)
    {
        return jObject[fieldName] != null;
    }
}

public abstract class JsonCreationConverter<T> : JsonConverter
{
    /// <summary>
    /// Create an instance of objectType, based properties in the JSON object
    /// </summary>
    /// <param name="objectType">type of object expected</param>
    /// <param name="jObject">
    /// contents of JSON object that will be deserialized
    /// </param>
    /// <returns></returns>
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, 
                                    Type objectType, 
                                     object existingValue, 
                                     JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }
}

6
WriteJson yönteminin de uygulanması ve türün dizilenmesi için bazı soyut yöntemler sağlanması iyi olur.
Triynko

54
NOT: Bu çözüm internetin her yerinde, ancak nadir durumlarda kendini gösteren bir kusura sahiptir. Yeni JsonReaderoluşturulan ReadJsonyönteme orijinal okuyucunun yapılandırma değerlerden herhangi (miras değil Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling, vs ...). Bu değerler new JsonReaderin kullanılmadan önce kopyalanmalıdır serializer.Populate().
Alain

9
Yeni JsonReader oluşturmayı önlemek için (@Alain tarafından belirtilen nedenlerden dolayı) veya üst öğenin değerinden bazılarına dayalı olarak oluşturulan nesne türüne karar vermeniz gerekiyorsa, bu çözüm stackoverflow.com/a/22539730/1038496 adresine bakın . Benim için daha etkili ve daha net görünüyor (bu tür bir problem için bile).
14'te Zoka

8
@Triynko: aramalardan uzun bir süre sonra, sınıf öğrendim, JsonConverteradlı bir özelliğe sahiptir CanReadve CanWrite. Özel bir WriteJsonuygulamaya ihtiyacınız yoksa , CanWritegeri dönmenize izin vermek yeterlidir FALSE. Sistem daha sonra varsayılan davranışa geri döner. @jdavies: Lütfen cevabınıza ekleyin. Aksi takdirde serileştirme sırasında çökecektir.
SimonSimCity

1
NULL vakaları işlemek gerektiğini buldum, aksi takdirde hoş bir hata oluşacaktır. Kullanımı: ||| if (reader.TokenType == JsonToken.Null) null değerini döndürür; |||| Kaynak: stackoverflow.com/a/34185296/857291
Cesar

96

Yukarıdaki çözüm JsonCreationConverter<T> internetin her yerinde, ancak nadir durumlarda kendini gösteren bir kusur var. ReadJson yönteminde oluşturulan yeni JsonReader, orijinal okuyucunun yapılandırma değerlerini (Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling vb.) Devralmaz. Bu değerler serializer.Populate () içinde yeni JsonReader kullanılmadan önce kopyalanmalıdır.

Bu yukarıdaki uygulama ile bazı sorunları düzeltmek için gelip en iyisi, ama yine de gözden kaçan bazı şeyler olduğunu düşünüyorum:

Güncelle Mevcut bir okuyucunun kopyasını oluşturan daha açık bir yöntem için bunu güncelledim. Bu, tek tek JsonReader ayarları üzerinden kopyalama işlemini kapsar. İdeal olarak bu işlev Newtonsoft kütüphanesinde korunur, ancak şimdilik aşağıdakileri kullanabilirsiniz:

/// <summary>Creates a new reader for the specified jObject by copying the settings
/// from an existing reader.</summary>
/// <param name="reader">The reader whose settings should be copied.</param>
/// <param name="jToken">The jToken to create a new reader for.</param>
/// <returns>The new disposable reader.</returns>
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
    JsonReader jTokenReader = jToken.CreateReader();
    jTokenReader.Culture = reader.Culture;
    jTokenReader.DateFormatString = reader.DateFormatString;
    jTokenReader.DateParseHandling = reader.DateParseHandling;
    jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
    jTokenReader.FloatParseHandling = reader.FloatParseHandling;
    jTokenReader.MaxDepth = reader.MaxDepth;
    jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
    return jTokenReader;
}

Bu aşağıdaki gibi kullanılmalıdır:

public override object ReadJson(JsonReader reader,
                                Type objectType,
                                object existingValue,
                                JsonSerializer serializer)
{
    if (reader.TokenType == JsonToken.Null)
        return null;
    // Load JObject from stream
    JObject jObject = JObject.Load(reader);
    // Create target object based on JObject
    T target = Create(objectType, jObject);
    // Populate the object properties
    using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
    {
        serializer.Populate(jObjectReader, target);
    }
    return target;
}

Eski çözüm şu şekildedir:

/// <summary>Base Generic JSON Converter that can help quickly define converters for specific types by automatically
/// generating the CanConvert, ReadJson, and WriteJson methods, requiring the implementer only to define a strongly typed Create method.</summary>
public abstract class JsonCreationConverter<T> : JsonConverter
{
    /// <summary>Create an instance of objectType, based properties in the JSON object</summary>
    /// <param name="objectType">type of object expected</param>
    /// <param name="jObject">contents of JSON object that will be deserialized</param>
    protected abstract T Create(Type objectType, JObject jObject);

    /// <summary>Determines if this converted is designed to deserialization to objects of the specified type.</summary>
    /// <param name="objectType">The target type for deserialization.</param>
    /// <returns>True if the type is supported.</returns>
    public override bool CanConvert(Type objectType)
    {
        // FrameWork 4.5
        // return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
        // Otherwise
        return typeof(T).IsAssignableFrom(objectType);
    }

    /// <summary>Parses the json to the specified type.</summary>
    /// <param name="reader">Newtonsoft.Json.JsonReader</param>
    /// <param name="objectType">Target type.</param>
    /// <param name="existingValue">Ignored</param>
    /// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
    /// <returns>Deserialized Object</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        //Create a new reader for this jObject, and set all properties to match the original reader.
        JsonReader jObjectReader = jObject.CreateReader();
        jObjectReader.Culture = reader.Culture;
        jObjectReader.DateParseHandling = reader.DateParseHandling;
        jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
        jObjectReader.FloatParseHandling = reader.FloatParseHandling;

        // Populate the object properties
        serializer.Populate(jObjectReader, target);

        return target;
    }

    /// <summary>Serializes to the specified type</summary>
    /// <param name="writer">Newtonsoft.Json.JsonWriter</param>
    /// <param name="value">Object to serialize.</param>
    /// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

7
CanWrite hakkında düşünmeyi unutmayın! (Yanlış olarak ayarladım) Kendi kendini referanslayan döngülerle bitirebilirim (yaptım). stackoverflow.com/questions/12314438/…
Dribbel

1
Ayrıca WriteJson'u uygulamanız gerekmez mi? Dönüştürücü nesneden json'a nasıl dönüştürüleceğini nasıl biliyor?
David S.

15

Ben sadece yansıma kullanarak Knowntype özniteliği ile çalışan bu da dayalı bir çözüm paylaşmak düşündüm, herhangi bir temel sınıftan türetilmiş sınıf almak zorunda kaldı, ben benim ihtiyaç yoktu ama çözüm en iyi eşleşen sınıf bulmak için özyineleme yararlanabilir büyük / küçük harf durumunda eşleştirme, bilinenTipleri varsa dönüştürücüye verilen tür tarafından yapılır, json dizesinin içindeki tüm özelliklere sahip bir türle eşleşene kadar hepsini tarar, ilk önce eşleşecek olanı seçilir.

kullanımı şu kadar basittir:

 string json = "{ Name:\"Something\", LastName:\"Otherthing\" }";
 var ret  = JsonConvert.DeserializeObject<A>(json, new KnownTypeConverter());

yukarıdaki durumda ret B tipi olacaktır.

JSON sınıfları:

[KnownType(typeof(B))]
public class A
{
   public string Name { get; set; }
}

public class B : A
{
   public string LastName { get; set; }
}

Dönüştürücü kodu:

/// <summary>
    /// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
    /// Selected class will be the first class to match all properties in the json object.
    /// </summary>
    public  class KnownTypeConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // Load JObject from stream
            JObject jObject = JObject.Load(reader);

            // Create target object based on JObject
            System.Attribute[] attrs = System.Attribute.GetCustomAttributes(objectType);  // Reflection. 

                // Displaying output. 
            foreach (System.Attribute attr in attrs)
            {
                if (attr is KnownTypeAttribute)
                {
                    KnownTypeAttribute k = (KnownTypeAttribute) attr;
                    var props = k.Type.GetProperties();
                    bool found = true;
                    foreach (var f in jObject)
                    {
                        if (!props.Any(z => z.Name == f.Key))
                        {
                            found = false;
                            break;
                        }
                    }

                    if (found)
                    {
                        var target = Activator.CreateInstance(k.Type);
                        serializer.Populate(jObject.CreateReader(),target);
                        return target;
                    }
                }
            }
            throw new ObjectNotFoundException();


            // Populate the object properties

        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

1
Bu çözümü gerçekten çok seviyorum, ancak aynı özellik adlarına sahip birden fazla bilinen tür olduğunda bir sorun görebilirsiniz. Bu problemle karşılaştın mı? Teşekkürler.
covo

8

JsonSubTypes projesi , özniteliklerin yardımıyla bu özelliği ele alan genel bir dönüştürücü uygular.

Burada sağlanan beton numunesi için şu şekilde çalışır:

    [JsonConverter(typeof(JsonSubtypes))]
    [JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")]
    [JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")]
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class Employee : Person
    {
        public string Department { get; set; }
        public string JobTitle { get; set; }
    }

    public class Artist : Person
    {
        public string Skill { get; set; }
    }

    [TestMethod]
    public void Demo()
    {
        string json = "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
                      "{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
                      "{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";


        var persons = JsonConvert.DeserializeObject<IReadOnlyCollection<Person>>(json);
        Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
    }

2
Çok kullanışlı bir dönüştürücü. Sadece kendimi bir dönüştürücü kodlama saatlerce kurtardı!
Carlos Rodriguez

7

Bu totem'in cevabına bir açılım. Temelde aynı şeyi yapar, ancak özellik eşleme .net nesnesini yansıtmaz, serileştirilmiş json nesnesine dayanır. [JsonProperty] kullanıyorsanız, CamelCasePropertyNamesContractResolver kullanıyorsanız veya json'un .net nesnesiyle eşleşmemesine neden olacak başka bir şey kullanıyorsanız bu önemlidir.

Kullanımı basit:

[KnownType(typeof(B))]
public class A
{
   public string Name { get; set; }
}

public class B : A
{
   public string LastName { get; set; }
}

Dönüştürücü kodu:

/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter {
    public override bool CanConvert( Type objectType ) {
        return System.Attribute.GetCustomAttributes( objectType ).Any( v => v is KnownTypeAttribute );
    }

    public override bool CanWrite {
        get { return false; }
    }

    public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
        // Load JObject from stream
        JObject jObject = JObject.Load( reader );

        // Create target object based on JObject
        System.Attribute[ ] attrs = System.Attribute.GetCustomAttributes( objectType );  // Reflection. 

        // check known types for a match. 
        foreach( var attr in attrs.OfType<KnownTypeAttribute>( ) ) {
            object target = Activator.CreateInstance( attr.Type );

            JObject jTest;
            using( var writer = new StringWriter( ) ) {
                using( var jsonWriter = new JsonTextWriter( writer ) ) {
                    serializer.Serialize( jsonWriter, target );
                    string json = writer.ToString( );
                    jTest = JObject.Parse( json );
                }
            }

            var jO = this.GetKeys( jObject ).Select( k => k.Key ).ToList( );
            var jT = this.GetKeys( jTest ).Select( k => k.Key ).ToList( );

            if( jO.Count == jT.Count && jO.Intersect( jT ).Count( ) == jO.Count ) {
                serializer.Populate( jObject.CreateReader( ), target );
                return target;
            }
        }

        throw new SerializationException( string.Format( "Could not convert base class {0}", objectType ) );
    }

    public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) {
        throw new NotImplementedException( );
    }

    private IEnumerable<KeyValuePair<string, JToken>> GetKeys( JObject obj ) {
        var list = new List<KeyValuePair<string, JToken>>( );
        foreach( var t in obj ) {
            list.Add( t );
        }
        return list;
    }
}

5

Totem'in bilinen tür çözümünde başka bir varyasyon olarak, bilinen tür niteliklerini kullanma ihtiyacını ortadan kaldırmak için genel bir tür çözümleyici oluşturmak için yansımayı kullanabilirsiniz.

Bu Juval Lowy'nin GenericResolver'ına benzer bir teknik kullanır WCF .

Temel sınıfınız soyut veya bir arabirim olduğu sürece, bilinen tür öznitelikleriyle dekore edilmek yerine bilinen türler otomatik olarak belirlenir.

Benim durumumda, özellik tabanlı belirleme kullanmak için burada diğer çözümlerden ödünç alabilirsiniz, ancak özelliklerinden belirlemeye çalışmak yerine benim json nesnesindeki türü belirtmek için bir $ türü özelliği kullanmayı tercih etti.

 public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter() : this(ReflectTypes())
    {

    }
    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x => typeName == x.Name));
        }
        else
        {
            return Activator.CreateInstance(objectType);
        }
        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    //Static helpers
    static Assembly CallingAssembly = Assembly.GetCallingAssembly();

    static Type[] ReflectTypes()
    {
        List<Type> types = new List<Type>();
        var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
        foreach (var assemblyName in referencedAssemblies)
        {
            Assembly assembly = Assembly.Load(assemblyName);
            Type[] typesInReferencedAssembly = GetTypes(assembly);
            types.AddRange(typesInReferencedAssembly);
        }

        return types.ToArray();
    }

    static Type[] GetTypes(Assembly assembly, bool publicOnly = true)
    {
        Type[] allTypes = assembly.GetTypes();

        List<Type> types = new List<Type>();

        foreach (Type type in allTypes)
        {
            if (type.IsEnum == false &&
               type.IsInterface == false &&
               type.IsGenericTypeDefinition == false)
            {
                if (publicOnly == true && type.IsPublic == false)
                {
                    if (type.IsNested == false)
                    {
                        continue;
                    }
                    if (type.IsNestedPrivate == true)
                    {
                        continue;
                    }
                }
                types.Add(type);
            }
        }
        return types.ToArray();
    }

Daha sonra bir formatlayıcı olarak kurulabilir

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonKnownTypeConverter());

1

İşte kullanımını önleyen jObject.CreateReader()ve bunun yerine yeni bir çözüm JsonTextReader(varsayılan JsonCreate.Deserialzeyöntem tarafından kullanılan davranış budur) :

public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        // Populate the object properties
        StringWriter writer = new StringWriter();
        serializer.Serialize(writer, jObject);
        using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString())))
        { 
            newReader.Culture = reader.Culture;
            newReader.DateParseHandling = reader.DateParseHandling;
            newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
            newReader.FloatParseHandling = reader.FloatParseHandling;
            serializer.Populate(newReader, target);
        }

        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

1

Çoğu zaman uygulama, arayüzle aynı ad alanında bulunacaktır. Ben de bununla geldim:

    public class InterfaceConverter : JsonConverter
    {
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.ReadFrom(reader);
        var typeVariable = this.GetTypeVariable(token);
        if (TypeExtensions.TryParse(typeVariable, out var implimentation))
        { }
        else if (!typeof(IEnumerable).IsAssignableFrom(objectType))
        {
            implimentation = this.GetImplimentedType(objectType);
        }
        else
        {
            var genericArgumentTypes = objectType.GetGenericArguments();
            var innerType = genericArgumentTypes.FirstOrDefault();
            if (innerType == null)
            {
                implimentation = typeof(IEnumerable);
            }
            else
            {
                Type genericType = null;
                if (token.HasAny())
                {
                    var firstItem = token[0];
                    var genericTypeVariable = this.GetTypeVariable(firstItem);
                    TypeExtensions.TryParse(genericTypeVariable, out genericType);
                }

                genericType = genericType ?? this.GetImplimentedType(innerType);
                implimentation = typeof(IEnumerable<>);
                implimentation = implimentation.MakeGenericType(genericType);
            }
        }

        return JsonConvert.DeserializeObject(token.ToString(), implimentation);
    }

    public override bool CanConvert(Type objectType)
    {
        return !typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.IsInterface || typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.GetGenericArguments().Any(t => t.IsInterface);
    }

    protected Type GetImplimentedType(Type interfaceType)
    {
        if (!interfaceType.IsInterface)
        {
            return interfaceType;
        }

        var implimentationQualifiedName = interfaceType.AssemblyQualifiedName?.Replace(interfaceType.Name, interfaceType.Name.Substring(1));
        return implimentationQualifiedName == null ? interfaceType : Type.GetType(implimentationQualifiedName) ?? interfaceType;
    }

    protected string GetTypeVariable(JToken token)
    {
        if (!token.HasAny())
        {
            return null;
        }

        return token.Type != JTokenType.Object ? null : token.Value<string>("$type");
    }
}

Bu nedenle, bunu küresel olarak şu şekilde ekleyebilirsiniz:

public static JsonSerializerSettings StandardSerializerSettings => new JsonSerializerSettings
    {
        Converters = new List<JsonConverter>
        {
            new InterfaceConverter()
        }
    };

1

Totem ve zlangner fikrini kullanarak, KnownTypeConverterjson verilerinin isteğe bağlı elemanlara sahip olmayabileceğini göz önünde bulundurarak, en uygun mirasçı belirleyebilecek bir tane oluşturdum .

Böylece, hizmet bir dizi belge (gelen ve giden) içeren bir JSON yanıtı gönderir. Belgelerin hem ortak öğeleri hem de farklı öğeleri vardır. Bu durumda, giden belgelerle ilgili öğeler isteğe bağlıdır ve bulunmayabilir.

Bu bağlamda, Documentortak bir özellik kümesi içeren bir temel sınıf oluşturulmuştur. İki miras sınıfı da oluşturulur: - OutgoingDocumentiki isteğe bağlı eleman ekler "device_id"ve "msg_id"; - IncomingDocumentbir zorunlu unsur ekler "sender_id";

Görev, json verilerine dayalı bir dönüştürücü oluşturmak ve KnownTypeAttribute'dan alınan bilgileri, alınan en büyük miktarda bilgiyi kaydetmenize izin veren en uygun sınıfı belirleyebilmektir. Ayrıca json verilerinin isteğe bağlı elemanlara sahip olmayabileceği de dikkate alınmalıdır. Json elemanlarının karşılaştırması sayısını ve veri modellerinin özelliklerini azaltmak için, temel sınıfın özelliklerini dikkate almamaya ve json elemanları ile sadece miras sınıflarının özelliklerini ilişkilendirmemeye karar verdim.

Servis verileri:

{
    "documents": [
        {
            "document_id": "76b7be75-f4dc-44cd-90d2-0d1959922852",
            "date": "2019-12-10 11:32:49",
            "processed_date": "2019-12-10 11:32:49",
            "sender_id": "9dedee17-e43a-47f1-910e-3a88ff6bc258",
        },
        {
            "document_id": "5044a9ac-0314-4e9a-9e0c-817531120753",
            "date": "2019-12-10 11:32:44",
            "processed_date": "2019-12-10 11:32:44",
        }
    ], 
    "total": 2
}

Veri modelleri:

/// <summary>
/// Service response model
/// </summary>
public class DocumentsRequestIdResponse
{
    [JsonProperty("documents")]
    public Document[] Documents { get; set; }

    [JsonProperty("total")]
    public int Total { get; set; }
}

// <summary>
/// Base document
/// </summary>
[JsonConverter(typeof(KnownTypeConverter))]
[KnownType(typeof(OutgoingDocument))]
[KnownType(typeof(IncomingDocument))]
public class Document
{
    [JsonProperty("document_id")]
    public Guid DocumentId { get; set; }

    [JsonProperty("date")]
    public DateTime Date { get; set; }

    [JsonProperty("processed_date")]
    public DateTime ProcessedDate { get; set; } 
}

/// <summary>
/// Outgoing document
/// </summary>
public class OutgoingDocument : Document
{
    // this property is optional and may not be present in the service's json response
    [JsonProperty("device_id")]
    public string DeviceId { get; set; }

    // this property is optional and may not be present in the service's json response
    [JsonProperty("msg_id")]
    public string MsgId { get; set; }
}

/// <summary>
/// Incoming document
/// </summary>
public class IncomingDocument : Document
{
    // this property is mandatory and is always populated by the service
    [JsonProperty("sender_sys_id")]
    public Guid SenderSysId { get; set; }
}

Dönüştürücü:

public class KnownTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
    }

    public override bool CanWrite => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // load the object 
        JObject jObject = JObject.Load(reader);

        // take custom attributes on the type
        Attribute[] attrs = Attribute.GetCustomAttributes(objectType);

        Type mostSuitableType = null;
        int countOfMaxMatchingProperties = -1;

        // take the names of elements from json data
        HashSet<string> jObjectKeys = GetKeys(jObject);

        // take the properties of the parent class (in our case, from the Document class, which is specified in DocumentsRequestIdResponse)
        HashSet<string> objectTypeProps = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Select(p => p.Name)
            .ToHashSet();

        // trying to find the right "KnownType"
        foreach (var attr in attrs.OfType<KnownTypeAttribute>())
        {
            Type knownType = attr.Type;
            if(!objectType.IsAssignableFrom(knownType))
                continue;

            // select properties of the inheritor, except properties from the parent class and properties with "ignore" attributes (in our case JsonIgnoreAttribute and XmlIgnoreAttribute)
            var notIgnoreProps = knownType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p => !objectTypeProps.Contains(p.Name)
                            && p.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute) && a.AttributeType != typeof(System.Xml.Serialization.XmlIgnoreAttribute)));

            //  get serializable property names
            var jsonNameFields = notIgnoreProps.Select(prop =>
            {
                string jsonFieldName = null;
                CustomAttributeData jsonPropertyAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(JsonPropertyAttribute));
                if (jsonPropertyAttribute != null)
                {
                    // take the name of the json element from the attribute constructor
                    CustomAttributeTypedArgument argument = jsonPropertyAttribute.ConstructorArguments.FirstOrDefault();
                    if(argument != null && argument.ArgumentType == typeof(string) && !string.IsNullOrEmpty((string)argument.Value))
                        jsonFieldName = (string)argument.Value;
                }
                // otherwise, take the name of the property
                if (string.IsNullOrEmpty(jsonFieldName))
                {
                    jsonFieldName = prop.Name;
                }

                return jsonFieldName;
            });


            HashSet<string> jKnownTypeKeys = new HashSet<string>(jsonNameFields);

            // by intersecting the sets of names we determine the most suitable inheritor
            int count = jObjectKeys.Intersect(jKnownTypeKeys).Count();

            if (count == jKnownTypeKeys.Count)
            {
                mostSuitableType = knownType;
                break;
            }

            if (count > countOfMaxMatchingProperties)
            {
                countOfMaxMatchingProperties = count;
                mostSuitableType = knownType;
            }
        }

        if (mostSuitableType != null)
        {
            object target = Activator.CreateInstance(mostSuitableType);
            using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
            {
                serializer.Populate(jObjectReader, target);
            }
            return target;
        }

        throw new SerializationException($"Could not serialize to KnownTypes and assign to base class {objectType} reference");
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    private HashSet<string> GetKeys(JObject obj)
    {
        return new HashSet<string>(((IEnumerable<KeyValuePair<string, JToken>>) obj).Select(k => k.Key));
    }

    public static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject)
    {
        JsonReader jObjectReader = jObject.CreateReader();
        jObjectReader.Culture = reader.Culture;
        jObjectReader.DateFormatString = reader.DateFormatString;
        jObjectReader.DateParseHandling = reader.DateParseHandling;
        jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
        jObjectReader.FloatParseHandling = reader.FloatParseHandling;
        jObjectReader.MaxDepth = reader.MaxDepth;
        jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
        return jObjectReader;
    }
}

PS: Benim durumumda, dönüştürücü tarafından hiç mirasçı seçilmemişse (bu JSON verileri yalnızca temel sınıftan bilgi içeriyorsa veya JSON verileri isteğe bağlı öğeler içermiyorsa oluşabilir OutgoingDocument), o zaman OutgoingDocumentsınıfın bir nesnesi , KnownTypeAttributeözellikler listesinde ilk sırada yer aldığından oluşturulacaktır. İsteğiniz üzerine, KnownTypeConverterbu durumda uygulanmasını değiştirebilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.