如何根据另一个JSON属性有条件地反序列化JSON对象?

时间:2016-07-13 17:10:50

标签: c# json json.net

假设我有以下模型类:

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

我已经创建了自定义ActionResultConverterJsonConverterResult属性Action以上的result注释,应该能够创建特定 {{ 1}} object 基于JSON的type属性中的字符串。

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

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

谢谢!

2 个答案:

答案 0 :(得分:7)

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

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

[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;
    }
}

请注意在ReadJson()内使用JsonSerializer.Populate()。这会自动填充除Action以外的Result的所有属性,从而无需对每个属性进行手动反序列化。

答案 1 :(得分:3)

灵感来自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();
    }
}

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