自定义 JsonConverter 用于将 JSON 序列化为抽象类

问题描述 投票:0回答:1

我正在反序列化一个复杂的json结构,如下所示,其中“events”下的“id”代表

"events": [
{
    "id": 1,
    "actions": [
    {
        "id": 8,
        "values": {
            "obj_id":160,
            "url": "https://www.myurl.com/"
        }
    },
    {
        "id":15,
        "values":{
            "obj_id":182,
            "scale":200
        }
    }
    ]
}
]
    

我有一个抽象的 ActionBase 类和多个派生类,不同的值基于不同的操作 id。

public class Events
{
    public int id;
    public ActionsHolder[] actions;
}

[JsonConverter(typeof(BaseConverter))]
public class ActionsHolder
{
    public int id { get; set; } = -1;
    public ActionBase values { get; set; }
}

public abstract class ActionBase : ActionsHolder
{
    public int obj_id { get; set; }
}

public class OpenURLAction : ActionBase
{
    //Action id 8 = Open URL
    public string url { get; set; }
}

public class ScaleAction : ActionBase
{
    //Action id 15 = Scale
    public float scale { get; set; } = 1;
}
public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
    protected override JsonConverter ResolveContractConverter(Type objectType)
    {
        if (typeof(ActionsHolder).IsAssignableFrom(objectType) && !objectType.IsAbstract)
        {
            return null; 
        }
            
        return base.ResolveContractConverter(objectType);
    }
}

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

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

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

        JObject joValues = (JObject)joAction["values"];
        switch (joAction["id"].Value<int>())
        {
            case 8:
                return new ActionsHolder
                {
                    id = joAction["id"].Value<int>(),
                    values = JsonConvert.DeserializeObject<OpenURLAction>(joValues.ToString(), SpecifiedSubclassConversion)
                };
            case 15:
                return new ActionsHolder
                {
                    id = joAction["id"].Value<int>(),
                    values = JsonConvert.DeserializeObject<ScaleAction>(joValues.ToString(), SpecifiedSubclassConversion)
                };
            default:
                return null;
        }
        throw new NotImplementedException();
    }

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

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

但是,这不起作用。 我的自定义转换器如何仅反序列化其他人的操作属性和默认转换器... 有人可以帮忙吗?

c# json json.net json-deserialization
1个回答
1
投票

我会修改您的类型层次结构和

JsonConverter
的设计,如下所示:

public class Root // Not shown in your question
{
    public List<Event> events { get; set; } = new();
}

public class Event
{
    public int id { get; set; } // Does this have a fixed value of 1?
    public List<ActionHolder> actions { get; set; } = new();
}

[JsonConverter(typeof(ActionHolderConverter))]
public class ActionHolder
{
    public int id => (int)(values?.id ?? ActionValueType.None);
    public ActionValueBase values { get; set; }
}

public enum ActionValueType
{
    None = -1,
    OpenURL = 8,
    Scale = 15,
}

public abstract class ActionValueBase
{
    [JsonIgnore] public abstract ActionValueType id { get; }
    public int obj_id { get; set; }
}

public class OpenURLActionValue : ActionValueBase
{
    public override ActionValueType id => ActionValueType.OpenURL;
    public string url { get; set; }
}

public class ScaleActionValue : ActionValueBase
{
    public override ActionValueType id => ActionValueType.Scale;
    public float scale { get; set; } = 1;
}

并重写你的

JsonConverter
如下:

class ActionHolderConverter : JsonConverter<ActionHolder>
{
    public override ActionHolder ReadJson(JsonReader reader, Type objectType, ActionHolder existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        JObject joAction = JObject.Load(reader);
        
        var values = (ActionValueType)joAction[nameof(ActionHolder.id)].Value<int>() switch
        {
            ActionValueType.Scale => joAction[nameof(ActionHolder.values)]?.ToObject<ScaleActionValue>(serializer),
            ActionValueType.OpenURL => joAction[nameof(ActionHolder.values)]?.ToObject<OpenURLActionValue>(serializer),
            ActionValueType.None => (ActionValueBase)null,
            var t => throw new JsonSerializationException($"Unknown ActionValueTypes {t}"),
        };
        var holder = existingValue ?? new ActionHolder();
        holder.values = values;
        return holder;
    }

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, ActionHolder value, JsonSerializer serializer) => throw new NotImplementedException();
}

要点:

  • 在您的设计中,

    ActionBase
    继承自
    ActionHolder
    ,但这似乎是一个错误。在 JSON 中,具有两个值
    events[*].actions[*]
    "id"
    "values"
    对象与具有
    "values"
    属性和其他属性的嵌套
    "obj_id"
    对象之间有明显的区别。由于这两者之间没有共同的属性,因此使用
    ActionBase
    作为两者的基类是没有意义的。

  • 我引入了一个

    enum
    对应于
    id
    的可能值。

  • 由于

    ActionHolder.id
    完全由值的类型定义,因此我将其设置为只读,并根据
    ActionValueBase
    的虚拟只读属性确定,而该属性又由类型确定。

  • 如果您有机会想要添加到

    events
    actions
    集合中,我建议使用可调整大小的
    List<T>
    列表,而不是固定长度的数组。

演示小提琴这里

© www.soinside.com 2019 - 2024. All rights reserved.