JsonConvert.DeserializeObject<> (字符串)为$ id属性返回null值

时间:2013-12-19 16:06:56

标签: c# json.net webclient

我正在使用System.Net.WebClient.DownloadString下载JSON。我收到了有效回复:

{
"FormDefinition": [
    {
        "$id":"4",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Punchworks Form"
    },
    {
        "$id":"6",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Punchworks Form test second"
    },
    {
        "$id":"46",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"any_Name"
    },
    {
        "$id":"47",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Punchworks Form test second"
    },
    {
        "$id":"49",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name ??´????? ???? ACEeišuu { [ ( ~ ! @ # "
    },
    {
        "$id":"50",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"something new"
    },
    {
        "$id":"56",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name руÌÑÑкий 汉语漢語 ĄČĘėįšųū { [ ( ~ ! @ # "
    },
    {
        "$id":"57",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Test Name"
    },
    {
        "$id":"58",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 12:59:29 PM"
    },
    {
        "$id":"59",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:01:18 PM"
    },
    {
        "$id":"60",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:40:44 PM"
    },
    {
        "$id":"61",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:43:46 PM"
    },
    {
        "$id":"62",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:48:21 PM"
    },
    {
        "$id":"63",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:57:00 PM"
    },
    {
        "$id":"64",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:57:53 PM"
    },
    {
        "$id":"65",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:58:46 PM"
    },
    {
        "$id":"79",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name1211"
    },
    {
        "$id":"80",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name1211"
    },
    {
        "$id":"81",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"any_nami"
    },
    {
        "$id":"90",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Test_something3"
    },
    {
        "$id":"91",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Test_something4"
    }]
}

这是我的模特:

public class FormDefinitionList
{
    [JsonProperty("FormDefinition")]
    public List<FormDefinition> FormDefinitions { get; set; }
}

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

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

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

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

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

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

当我这样做时,一切都有效:

string response = "json as above";
FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList> (response);

除了Id($ id)属性始终为null。起初我试图弄清楚我从服务器回来的美元符号是不同的,但事实并非如此。我不知道从哪里开始,所以有什么想法吗?

提前致谢。

注意:如果我尝试使用JavaScriptSerializer之类的方式进行反序列化,它可以完美地运行,所以我很确定我的模型或JSON.net出了问题。可能是错的。

3 个答案:

答案 0 :(得分:11)

Json.Net通常使用$id$ref作为元数据来保存JSON中的对象引用。因此,当它看到$id时,它假定该属性不是实际JSON属性集的一部分,而是内部标识符。因此,它不会填充对象上的Id属性,即使您包含一个[JsonProperty]属性指示它应该。

<强>更新

从Json.Net版本6.0.4开始,有一个新设置可以指示反序列化器将这些“元数据”属性视为普通属性而不是消耗它们。您需要做的就是将MetadataPropertyHandling设置为Ignore,然后照常反序列化。

var settings = new JsonSerializerSettings();
settings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;

var obj = JsonConvert.DeserializeObject<FormDefinitionList>(json, settings);

在6.0.4版之前,需要一种解决方法来解决此问题。本答案的其余部分讨论了可能的解决方法。如果您使用的是6.0.4或更高版本,则不需要解决方法,现在可以停止阅读。


我可以看到的最简单的解决方法是在反序列化之前在JSON上用"$id"(包括引号)替换"id",正如@Carlos Coelho建议的那样。由于您必须对每个响应执行此操作,如果您采用此路由,我建议您使用简单的帮助方法来避免代码重复,例如:

public static T Deserialize<T>(string json)
{
    return JsonConvert.DeserializeObject<T>(json.Replace("\"$id\"", "\"id\""));
}

但是,既然你在评论中说过你不是那么热衷于使用字符串替换的想法,我会调查其他选项。我找到了另一个可能对你有用的替代方案 - 自定义JsonConverter。转换器背后的想法是它会尝试使用Json.Net的内置反序列化机制来创建和填充对象(无ID),然后从JSON手动检索$id属性并使用它来填充通过反射在对象上的Id属性。

以下是转换器的代码:

public class DollarIdPreservingConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(FormDefinition);
    }

    public override object ReadJson(JsonReader reader, Type objectType,
                           object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        object o = jo.ToObject(objectType);
        JToken id = jo["$id"];
        if (id != null)
        {
            PropertyInfo prop = objectType.GetProperty("Id");
            if (prop != null && prop.CanWrite && 
                prop.PropertyType == typeof(string))
            {
                prop.SetValue(o, id.ToString(), null);
            }
        }
        return o;
    }

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

我尝试编写转换器,使其适用于具有$id的任何对象 - 您只需要相应地更改CanConvert方法,以便为所有类型返回true除FormDefinition之外,您还需要使用它。

要使用转换器,只需将其实例传递给DeserializeObject<T>,如下所示:

FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList>(
                                      json, new DollarIdPreservingConverter());

重要提示:您可能想要使用JsonConverter属性修饰您的课程,而不是将转换器传递到DeserializeObject来电,但不要这样做 - - 它将导致转换器进入递归循环,直到堆栈溢出。 (有一种方法可以使转换器使用该属性,但您必须重写ReadJson方法以手动创建目标对象并填充其属性而不是调用jo.ToObject(objectType)。这是可行的,但有点麻烦。)

请告诉我这是否适合您。

答案 1 :(得分:0)

问题是$符号,因此解决方法是:

从JsonProperty注释中删除$。

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

在您的代码中,替换特殊字符$

string response = "json as above";
FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList> (response.Replace("$id","id"));

编辑为@BrianRogers建议

答案 2 :(得分:0)

这个答案为我修复了$ id / $ ref问题:Json.Net adding $id to EF objects despite setting PreserveReferencesHandling to "None"

在DefaultContractResolver / IContractResolver的实现中,添加此内容;

public override JsonContract ResolveContract(Type type) {
    var contract = base.ResolveContract(type);
    contract.IsReference = false;
    return contract;
}

编辑:这将删除$ id。