【问题标题】:How to conditionally deserialize JSON object based on another JSON property?如何基于另一个 JSON 属性有条件地反序列化 JSON 对象?
【发布时间】:2016-07-13 17:10:49
【问题描述】:

假设我有以下模型类:

public class Action
{
    public enum Type
    {
        Open,
        Close,
        Remove,
        Delete,
        Reverse,
        Alert,
        ScaleInOut,
        Nothing
    }

    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("active")]
    [JsonConverter(typeof(IntToBoolConverter))]
    public bool Active { get; set; }

    [JsonProperty("type")]
    [JsonConverter(typeof(ActionTypeConverter))]
    public Type ActionType { get; set; }

    [JsonProperty("result")]
    [JsonConverter(typeof(ActionResultConverter))]
    public ActionResult Result { get; set; }
}

我想将以下 JSON 反序列化到该类中:

{
    "name":"test1",
    "id":"aa0832f0508bb580ce7f0506132c1c13",
    "active":"1",
    "type":"open",
    "result":{
        "property1":"buy",
        "property2":"123.123",
        "property3":"2016-07-16T23:00:00",
        "property4":"768",
        "property5":true
     }
}

结果对象每次都可以不同(6个模型之一),其类型取决于JSON属性type

我已经创建了自定义ActionResultConverterResult 属性上方的JsonConverter 注释Action 类)应该能够创建特定 result 对象 基于 JSON 的 type 属性中的字符串。

我的问题是我不知道如何从转换器访问该属性,因为只有整个 JSON 的 result 部分被传递给 JsonReader

任何想法或帮助将不胜感激。

谢谢!

【问题讨论】:

  • 您可以反序列化为与 json 结构匹配的类,并使用该类来确定您希望数据最终进入哪种类型的自定义类。或者使用动态。鸡肉培根牧场。
  • 另外,您的type 属性是openopen 的枚举值为 0。字符串到整数的转换不是很好。如果您反序列化为与 json 结构匹配的类,然后转换为最终游戏类,则此转换也可能是您从字符串转换为 int (Enum) 的地方。
  • 另外,您将枚举命名为与流行的系统类相同:msdn.microsoft.com/en-us/library/system.type(v=vs.110).aspx 可能会让一些维护系统的人感到困惑。
  • @SteveG 反序列化type 属性没有问题,因为我也为此编写了自定义转换器,并且效果很好。使用dynamic 是什么意思?我应该将 JSON 序列化为 dynamic 并手动将 JSON 解析为我的 Action 类?
  • 而且命名是故意的。只有在该类内部,它可能会令人困惑,因为使用了 Type 名称本身。但在外面它必须用作Action.TypeAction.ActionType 对我来说似乎有点奇怪:)

标签: c# json json.net


【解决方案1】:

Json.NET 不提供在反序列化子对象时访问 JSON 层次结构中父对象属性值的方法。这可能是因为根据standard,JSON 对象被定义为 一组无序的名称/值对,因此不能保证所需的父属性出现在子元素之前JSON 流。

因此,您需要在 Action 本身的转换器中处理 Type 属性,而不是在 ActionResult 的转换器中处理:

[JsonConverter(typeof(ActionConverter))]
public class Action
{
    readonly static Dictionary<Type, System.Type> typeToSystemType;
    readonly static Dictionary<System.Type, Type> systemTypeToType;

    static Action()
    {
        typeToSystemType = new Dictionary<Type, System.Type>
        {
            { Type.Open, typeof(OpenActionResult) },
        };
        systemTypeToType = typeToSystemType.ToDictionary(p => p.Value, p => p.Key);
    }

    public static Type SystemTypeToType(System.Type systemType)
    {
        return systemTypeToType[systemType];
    }

    public static System.Type TypeToSystemType(Type type)
    {
        return typeToSystemType[type];
    }

    public enum Type
    {
        Open,
        Close,
        Remove,
        Delete,
        Reverse,
        Alert,
        ScaleInOut,
        Nothing
    }

    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("active")]
    [JsonConverter(typeof(IntToBoolConverter))]
    public bool Active { get; set; }

    [JsonProperty("type")]
    [JsonConverter(typeof(ActionTypeConverter))]
    public Type ActionType { get; set; }

    [JsonProperty("result")]
    public ActionResult Result { get; set; }
}

class ActionConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var obj = JObject.Load(reader);
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
        var action = existingValue as Action ?? (Action)contract.DefaultCreator();

        // Remove the Result property for manual deserialization
        var result = obj.GetValue("Result", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();

        // Populate the remaining properties.
        using (var subReader = obj.CreateReader())
        {
            serializer.Populate(subReader, action);
        }

        // Process the Result property
        if (result != null)
            action.Result = (ActionResult)result.ToObject(Action.TypeToSystemType(action.ActionType));

        return action;
    }

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

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

public static class JsonExtensions
{
    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
        if (contained != null)
            contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (node.Parent is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
}

注意JsonSerializer.Populate()ReadJson() 中的使用。这会自动填充除Result 之外的Action 的所有属性,无需手动反序列化每个属性。

【讨论】:

    【解决方案2】:

    灵感来自http://json.codeplex.com/discussions/56031:

    public sealed class ActionModelConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(ActionModel).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jObject = JObject.Load(reader);
            ActionModel actionModel = new ActionModel();
    
            // TODO: Manually populate properties
            actionModel.Id = (string)jObject["id"].ToObject<string>();
    
            var type = (ActionModel.Type)jObject["type"].ToObject<ActionModel.Type>();
            switch (type)
            {
              case ActionModel.Type.Open:
                var actionResult = jObject["result"].ToObject<ActionOpenResult>(jsonSerializer);
    
              default:
                throw new JsonSerializationException($"Unsupported action type: '{type}'");
            }
    
            actionModel.Result = actionResult;
    
            return actionModel;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    在编辑器中编码,很抱歉错别字:)

    【讨论】:

      猜你喜欢
      • 2022-01-17
      • 1970-01-01
      • 2019-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-23
      相关资源
      最近更新 更多