bestsource

JSON을 추상 클래스로 역직렬화하는 중

bestsource 2023. 3. 15. 19:48
반응형

JSON을 추상 클래스로 역직렬화하는 중

JSON 문자열을 추상 클래스에서 상속받는 구체적인 클래스로 역직렬화하려고 하는데 제대로 작동하지 않습니다.구글로 검색해서 몇 가지 해결책을 시도해 봤지만 역시 효과가 없는 것 같습니다.

지금 가지고 있는 것은 다음과 같습니다.

abstract class AbstractClass { }

class ConcreteClass { }

public AbstractClass Decode(string jsonString)
{
    JsonSerializerSettings jss = new JsonSerializerSettings();
    jss.TypeNameHandling = TypeNameHandling.All;
    return (AbstractClass)JsonConvert.DeserializeObject(jsonString, null, jss);
}

하지만 결과물을 캐스팅하려고 하면 효과가 없어요.

Diserialize Object를 사용하지 않는 이유는 구체적인 클래스가 많기 때문입니다.

좋은 의견이라도 있나?

  • 저는 뉴턴소프트를 사용하고 있습니다.제이슨

Type Name Handling은 사용하지 않을 수 있습니다(좀 더 콤팩트한 json을 원하거나 "$type" 이외의 유형 변수에 특정 이름을 사용하고 싶기 때문입니다).한편 custom Creation Converter 접근방식은 어떤 클래스를 사용해야 할지 미리 알지 못한 상태에서 기본 클래스를 여러 파생 클래스로 역직렬화하려는 경우 작동하지 않습니다.

또는 기본 클래스에서 int 또는 다른 유형을 사용하여 JsonConverter를 정의하는 방법도 있습니다.

[JsonConverter(typeof(BaseConverter))]
abstract class Base
{
    public int ObjType { get; set; }
    public int Id { get; set; }
}

class DerivedType1 : Base
{
    public string Foo { get; set; }
}

class DerivedType2 : Base
{
    public string Bar { get; set; }
}

그런 다음 기본 클래스의 JsonConverter는 유형에 따라 개체를 역직렬화할 수 있습니다.복잡한 점은 스택오버플로우(JsonConverter가 반복적으로 콜을 발신하는 경우)를 피하기 위해 이 역직렬화 중에 커스텀계약 리졸바를 사용해야 한다는 점입니다.

public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
    protected override JsonConverter ResolveContractConverter(Type objectType)
    {
        if (typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract)
            return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
        return base.ResolveContractConverter(objectType);
    }
}

public class BaseConverter : JsonConverter
{
    static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new BaseSpecifiedConcreteClassConverter() };

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Base));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        switch (jo["ObjType"].Value<int>())
        {
            case 1:
                return JsonConvert.DeserializeObject<DerivedType1>(jo.ToString(), SpecifiedSubclassConversion);
            case 2:
                return JsonConvert.DeserializeObject<DerivedType2>(jo.ToString(), SpecifiedSubclassConversion);
            default:
                throw new Exception();
        }
        throw new NotImplementedException();
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException(); // won't be called because CanWrite returns false
    }
}

바로 그겁니다.이제 파생 클래스를 serialize/deserialize를 사용할 수 있습니다.다른 클래스에서 기본 클래스를 사용하여 추가 작업 없이 해당 클래스를 직렬화/비직렬화할 수도 있습니다.

class Holder
    {
        public List<Base> Objects { get; set; }
    }
string json = @"
        [
            {
                ""Objects"" : 
                [
                    { ""ObjType"": 1, ""Id"" : 1, ""Foo"" : ""One"" },
                    { ""ObjType"": 1, ""Id"" : 2, ""Foo"" : ""Two"" },
                ]
            },
            {
                ""Objects"" : 
                [
                    { ""ObjType"": 2, ""Id"" : 3, ""Bar"" : ""Three"" },
                    { ""ObjType"": 2, ""Id"" : 4, ""Bar"" : ""Four"" },
                ]
            },
        ]";

            List<Holder> list = JsonConvert.DeserializeObject<List<Holder>>(json);
            string serializedAgain = JsonConvert.SerializeObject(list);
            Debug.WriteLine(serializedAgain);

Custom Creation Converter를 사용하는 방법은 다음과 같습니다.

public enum ClassDiscriminatorEnum
    {
        ChildClass1,
        ChildClass2
    }

    public abstract class BaseClass
    {
        public abstract ClassDiscriminatorEnum Type { get; }
    }

    public class Child1 : BaseClass
    {
        public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass1;
        public int ExtraProperty1 { get; set; }
    }

    public class Child2 : BaseClass
    {
        public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass2;
    }

    public class BaseClassConverter : CustomCreationConverter<BaseClass>
    {
        private ClassDiscriminatorEnum _currentObjectType;

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jobj = JObject.ReadFrom(reader);
            _currentObjectType = jobj["Type"].ToObject<ClassDiscriminatorEnum>();
            return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
        }

        public override BaseClass Create(Type objectType)
        {
            switch (_currentObjectType)
            {
                case ClassDiscriminatorEnum.ChildClass1:
                    return new Child1();
                case ClassDiscriminatorEnum.ChildClass2:
                    return new Child2();
                default:
                    throw new NotImplementedException();
            }
        }
    }

이와 같은 것을 시도하다

public AbstractClass Decode(string jsonString)
{
    var jss = new JavaScriptSerializer();
    return jss.Deserialize<ConcreteClass>(jsonString);
}

갱신하다
이 시나리오에서는 모든 것이 당신이 원하는 대로 작동해야 합니다.

public abstract class Base
{
    public abstract int GetInt();
}
public class Der:Base
{
    int g = 5;
    public override int GetInt()
    {
        return g+2;
    }
}
public class Der2 : Base
{
    int i = 10;
    public override int GetInt()
    {
        return i+17;
    }
}

....

var jset = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };
Base b = new Der()
string json = JsonConvert.SerializeObject(b, jset);
....

Base c = (Base)JsonConvert.DeserializeObject(json, jset);

어디에c타입은test.Base {test.Der}

갱신하다

@Gusman은 사용을 제안한다.TypeNameHandling.Objects대신TypeNameHandling.All이것으로 충분하고, 보다 장황한 시리얼화를 실현할 수 있습니다.

실제로 업데이트에서 설명한 바와 같이, (2019년에) 가장 간단한 방법은 여기에 설명된 바와 같이 간단한 사용자 정의 JsonSerializerSettings를 사용하는 것입니다.

        string jsonTypeNameAll = JsonConvert.SerializeObject(priceModels, Formatting.Indented,new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All
        });

디시리얼라이징의 경우:

TDSPriceModels models = JsonConvert.DeserializeObject<TDSPriceModels>(File.ReadAllText(jsonPath), new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All
        });
 public class CustomConverter : JsonConverter
{
    private static readonly JsonSerializer Serializer = new JsonSerializer();

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);
        var typeString = jObject.Value<string>("Kind"); //Kind is a property in json , from which we know type of child classes
        var requiredType = RecoverType(typeString);

        return Serializer.Deserialize(jObject.CreateReader(), requiredType);
    }

    private Type RecoverType(string typeString)
    {
        if (typeString.Equals(type of child class1, StringComparison.OrdinalIgnoreCase))
            return typeof(childclass1);
        if (typeString.Equals(type of child class2, StringComparison.OrdinalIgnoreCase))
            return typeof(childclass2);            

        throw new ArgumentException("Unrecognized type");
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(Base class).IsAssignableFrom(objectType) || typeof((Base class) == objectType;
    }

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

이 컨버터를 다음과 같이 JsonSerializerSettings에 추가합니다.

   var jsonSerializerSettings = new JsonSerializerSettings();
        jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        jsonSerializerSettings.Converters.Add(new CustomConverter());

다음과 같이 기본 클래스 개체를 직렬화 또는 직렬화 해제한 후

 JsonConvert.DeserializeObject<Type>("json string", jsonSerializerSettings );

저도 비슷한 문제가 있어서 다른 방법으로 해결했습니다.이것이 누군가에게 도움이 될지도 모릅니다.항상 다른 유형의 클래스가 될 수 있는 "data"라는 필드를 제외하고 항상 동일한 여러 필드가 포함된 json이 있습니다.모든 특정 파일을 분석하지 않고 연속성을 해제하고 싶습니다.솔루션은 다음과 같습니다.기본 클래스('Data' 필드 포함)를 정의하려면 Data 필드가 T 유형입니다.시리얼을 해제할 때마다 다음 유형을 지정합니다.

메인 클래스:

public class MainClass<T>
{
    [JsonProperty("status")]
    public Statuses Status { get; set; }

    [JsonProperty("description")]
    public string Description { get; set; }

    [JsonProperty("data")]
    public T Data { get; set; }

    public static MainClass<T> Parse(string mainClsTxt)
    {
        var response = JsonConvert.DeserializeObject<MainClass<T>>(mainClsTxt);
        return response;

    }
} 

사용자

public class User
{

    [JsonProperty("id")]
    public int UserId { get; set; }

    [JsonProperty("first_name")]
    public string FirstName { get; set; }

    [JsonProperty("last_name")]
    public string LastName { get; set; }

}

제품.

public class Product
{

    [JsonProperty("product_id")]
    public int ProductId { get; set; }

    [JsonProperty("product_name")]
    public string ProductName { get; set; }

    [JsonProperty("stock")]
    public int Stock { get; set; }

}

사용.

var v = MainClass<User>.Parse(userJson);
var v2 = MainClass<Product>.Parse(productJson);

json 예시

userJson: "{"status":1,"description":"my description","data":{"id":12161347,"first_name":"my fname","last_name":"my lname"}}"

productJson: "{"status":1,"description":"my description","data":{"product_id":5,"product_name":"my product","stock":1000}}"
public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

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

    public override object ReadJson(JsonReader reader,Type objectType,
        object existingValue, JsonSerializer serializer)
    {
        try
        {
            var jObject = JObject.Load(reader);
            var target = Create(objectType, jObject);
            serializer.Populate(jObject.CreateReader(), target);
            return target;
        }
        catch (JsonReaderException)
        {
            return null;
        }
    }

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

이 인터페이스를 구현합니다.

public class SportActivityConverter : JsonCreationConverter<BaseSportActivity>
{
    protected override BaseSportActivity Create(Type objectType, JObject jObject)
    {
        BaseSportActivity result = null;
        try
        {
            switch ((ESportActivityType)jObject["activityType"].Value<int>())
            {
                case ESportActivityType.Football:
                    result = jObject.ToObject<FootballActivity>();
                    break;

                case ESportActivityType.Basketball:
                    result = jObject.ToObject<BasketballActivity>();
                    break;
            }
            
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex);
        }

        return result;
        
    }
}

언급URL : https://stackoverflow.com/questions/20995865/deserializing-json-to-abstract-class

반응형