program tip

인터페이스 인스턴스 컬렉션을 역 직렬화 하시겠습니까?

radiobox 2020. 12. 10. 19:47
반응형

인터페이스 인스턴스 컬렉션을 역 직렬화 하시겠습니까?


이 코드를 json.net을 통해 직렬화하고 싶습니다.

public interface ITestInterface
{
    string Guid {get;set;}
}

public class TestClassThatImplementsTestInterface1
{
    public string Guid { get;set; }
}

public class TestClassThatImplementsTestInterface2
{
    public string Guid { get;set; }
}


public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {             
         this.CollectionToSerialize = new List<ITestInterface>();
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
    }
    List<ITestInterface> CollectionToSerialize { get;set; }
}

ClassToSerializeViaJson을 json.net으로 직렬화 / 역 직렬화하고 싶습니다. 직렬화가 작동하지만 역 직렬화로 인해 다음 오류가 발생합니다.

Newtonsoft.Json.JsonSerializationException : ITestInterface 유형의 인스턴스를 만들 수 없습니다. 유형은 인터페이스 또는 추상 클래스이며 인스턴스화 할 수 없습니다.

그렇다면 List<ITestInterface>컬렉션을 어떻게 역 직렬화 할 수 있습니까?


원하는 작업에 대한 전체 작업 예제 :

public interface ITestInterface
{
    string Guid { get; set; }
}

public class TestClassThatImplementsTestInterface1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class TestClassThatImplementsTestInterface2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {
        this.CollectionToSerialize = new List<ITestInterface>();
    }
    public List<ITestInterface> CollectionToSerialize { get; set; }
}

public class TypeNameSerializationBinder : SerializationBinder
{
    public string TypeFormat { get; private set; }

    public TypeNameSerializationBinder(string typeFormat)
    {
        TypeFormat = typeFormat;
    }

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = serializedType.Name;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        var resolvedTypeName = string.Format(TypeFormat, typeName);
        return Type.GetType(resolvedTypeName, true);
    }
}

class Program
{
    static void Main()
    {
        var binder = new TypeNameSerializationBinder("ConsoleApplication.{0}, ConsoleApplication");
        var toserialize = new ClassToSerializeViaJson();

        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface1()
            {
                Guid = Guid.NewGuid().ToString(), Something1 = "Some1"
            });
        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface2()
            {
                Guid = Guid.NewGuid().ToString(), Something2 = "Some2"
            });

        string json = JsonConvert.SerializeObject(toserialize, Formatting.Indented, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder
            });
        var obj = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(json, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder 
            });

        Console.ReadLine();
    }
}

직접 시도하는 동안이 질문을 찾았습니다. Piotr Stapp의 (Garath의) 답변을 구현 한 후 얼마나 단순 해 보 였는지에 놀랐 습니다. 인스턴스화하려는 정확한 유형 (문자열)이 이미 전달 된 메서드를 구현하는 것뿐이라면 라이브러리가 자동으로 바인딩하지 않는 이유는 무엇입니까?

실제로 사용자 지정 바인더가 필요하지 않다는 사실을 발견했습니다. Json.Net은 제가하고있는 작업이라고 말하면 필요한 작업을 정확히 수행 할 수있었습니다.

직렬화 할 때 :

string serializedJson = JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

직렬화 해제시 :

var deserializedObject = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(serializedJson, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects
});

관련 문서 : Json.NET에 대한 직렬화 설정TypeNameHandling 설정


나는 또한 Garath의 단순성에 놀랐고 Json 라이브러리가 자동으로 할 수 있다는 결론에 도달했습니다. 그러나 나는 또한 Ben Jenkinson의 대답보다 훨씬 간단하다고 생각했습니다 (json 라이브러리 개발자가 직접 수정 한 것을 볼 수 있지만). 내 테스트에서 다음과 같이 TypeNameHandling을 Auto로 설정하기 만하면됩니다.

var objectToSerialize = new List<IFoo>();
// TODO: Add objects to list
var jsonString = JsonConvert.SerializeObject(objectToSerialize,
       new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
var deserializedObject = JsonConvert.DeserializeObject<List<IFoo>>(jsonString, 
       new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });

에서 TypeNameHandling 열거 문서

자동 : 직렬화되는 개체의 유형이 선언 된 유형과 동일하지 않은 경우 .NET 유형 이름을 포함합니다. 여기에는 기본적으로 루트 직렬화 된 객체가 포함되지 않습니다.


기본 설정을 사용하면 할 수 없습니다. JSON.NET은 배열을 역 직렬화하는 방법을 알 수 없습니다. 그러나 인터페이스 유형에 사용할 유형 변환기를 지정할 수 있습니다. 이를 수행하는 방법을 보려면 다음 페이지를 참조하십시오. http://blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/

이 SO 질문에서이 문제에 대한 정보를 찾을 수도 있습니다. JSON.NET에서 역 직렬화를위한 인터페이스 캐스팅


이것은 오래된 질문이지만 더 자세한 답변을 추가 할 것이라고 생각했습니다 (내가 작성한 기사 형식) : http://skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet /

TLDR : 직렬화 된 JSON에 형식 이름을 포함하도록 Json.NET을 구성하는 대신 JSON 변환기를 사용하여 원하는 사용자 지정 논리를 사용하여 역 직렬화 할 클래스를 파악할 수 있습니다.

이것은 deserialization 중단에 대해 걱정하지 않고 유형을 리팩터링 할 수 있다는 이점이 있습니다.


애플리케이션에서 직렬화하지 않은 JSON을 역 직렬화하고 싶었 기 때문에 구체적인 구현을 수동으로 지정해야했습니다. 나는 Nicholas의 대답을 확장했습니다.

우리가 가지고 있다고 가정합시다

public class Person
{
    public ILocation Location { get;set; }
}

그리고 구체적인 인스턴스

public class Location: ILocation
{
    public string Address1 { get; set; }
    // etc
}

이 수업에 추가

public class ConfigConverter<I, T> : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(I);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var deserialized = (T)Activator.CreateInstance(typeof(T));
        serializer.Populate(jsonObject.CreateReader(), deserialized);
        return deserialized;
    }
}

그런 다음 JsonConverter 속성으로 인터페이스를 정의하십시오.

public class Person
{
    [JsonConverter(typeof(ConfigConverter<ILocation, Location>))]
    public ILocation Location { get;set; }
}

JSON.NET 및 JsonSubTypes 속성 으로 수행 할 수 있습니다 .

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test1), "Something1")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test2), "Something2")]
public interface ITestInterface
{
    string Guid { get; set; }
}

public class Test1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class Test2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

그리고 간단히 :

var fromCode = new List<ITestInterface>();
// TODO: Add objects to list
var json = JsonConvert.SerializeObject(fromCode);
var fromJson = JsonConvert.DeserializeObject<List<ITestInterface>>(json);

Inrego의 답변과 거의 중복되지만 추가 설명 할 가치가 있습니다.

If you use TypeNameHandling.Auto then it only includes the type/assembly name when it needs to (i.e. interfaces and base/derived classes). So your JSON is cleaner, smaller, more specific.

Which isn't that one of the main selling points of it over XML/SOAP?


Avoid TypeNameHandling.Auto when possible, particularly with user-controllable values.

You will need to write your own deserializer for the collection type.

Rather than repeat others who have already posted boilerplate converter code (particularly Nicholas Westby, whose blog post was quite useful and is linked above), I have included the relevant changes for deserializing a collection of interfaces (I had an enum interface property to distinguish implementors):

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        Collection<T> result = new Collection<T>();
        var array = JArray.Load(reader);
        foreach (JObject jsonObject in array)
        { 
            var rule = default(T);
            var value = jsonObject.Value<string>("MyDistinguisher");
            MyEnum distinguisher;
            Enum.TryParse(value, out distinguisher);
            switch (distinguisher)
            {
                case MyEnum.Value1:
                    rule = serializer.Deserialize<Type1>(jsonObject.CreateReader());
                    break;
                case MyEnum.Value2:
                    rule = serializer.Deserialize<Type2>(jsonObject.CreateReader());
                    break;
                default:
                    rule = serializer.Deserialize<Type3>(jsonObject.CreateReader());
                    break;
            }
            result.Add(rule);
        }
        return result;
    }

I hope this is helpful to the next person looking for an interface collection deserializer.

참고URL : https://stackoverflow.com/questions/15880574/deserialize-collection-of-interface-instances

반응형