JsonConvert.DeserializeObject<>(string)无法返回$id属性的空值

9
我正在使用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 出了问题。可能是我错了。

尝试不要使用 $。我建议不要将特殊字符作为键。 - Brad M
2
@BradM 感谢您的建议。不幸的是,由于 Api 不在我的控制范围之内,这不是一个选项。虽然这可能不被推荐,但它经常被使用,不应该是这个问题的原因。还有其他想法吗? - itslittlejohn
3个回答

15
Json.Net通常使用$id$ref作为元数据来保留JSON中的对象引用。因此,当它看到$id时,它会假定该属性不是实际JSON属性集的一部分,而是内部标识符。因此,即使您包含了一个[JsonProperty]属性指示它应该,它也不会填充对象上的Id属性。
更新:
从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方法,使其返回所有您需要在其中使用它的类型以外的FormDefinition为true。

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

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

重要提示:您可能会想用JsonConverter属性装饰您的类,而不是将转换器传递到DeserializeObject调用中,但不要这样做——它会导致转换器进入递归循环,直到堆栈溢出。(有一种方法可以让转换器与属性一起使用,但您必须重新编写ReadJson方法,手动创建目标对象并填充其属性,而不是调用jo.ToObject(objectType)。它是可行的,但有点混乱。)
如果这对您有用,请告诉我。

谢谢你的额外努力,Brian。我测试了你的转换器,它运行得非常好。不过,在prop.SetValue(o, id.ToString());这一行中,我遇到了语法问题。我需要在最后加上null作为第三个参数才能编译通过。你觉得这样说通吗? - itslittlejohn
我猜你可能在使用较旧版本的 .Net 框架(我在用 4.5 版本)。SetValue 的第三个参数是用于处理属性为索引器的情况,但这里并不是。因此,在第三个参数中传递 null 是有意义且正确的,以使其正常工作。 - Brian Rogers

0
问题在于美元符号,因此解决方法是:
从JsonProperty注释中删除$。
[JsonProperty ("id")]
public string Id { get; set; }

在你的代码中,替换特殊字符 $。
string response = "json as above";
FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList> (response.Replace("$id","id"));

根据@BrianRogers的建议进行了编辑


我想指出,$ 符号在 JSON.net 中是完全允许的:https://dev59.com/FG445IYBdhLWcg3w6eVo。使用 String.replace 显然不是一个好的选择,因为我的响应体中可能有 $ 字符,而我希望保留它们。 - itslittlejohn
2
@itslittlejohn 问题不在于 $ 符号本身,而是 $id 整体。Json.Net 使用此符号(以及 $ref)来保留 JSON 中的对象引用。因此,当它看到 $id 时,它会认为该属性不是实际的 JSON 属性集的一部分,而是一个内部标识符。在反序列化之前将 "$id" 替换为 "id"(包括引号字符)可能是一个可行的解决方法。这个字符序列不太可能出现在你实际的响应体中。如果我有更多时间,我会进一步研究并寻找更好的解决方案。 - Brian Rogers
@BrianRogers 谢谢Brian。如果您知道一个好的处理方法,我会非常感激。我想字符串替换可能会起作用,但我不想为每个来自服务器的响应都这样做。 - itslittlejohn

0

2
иҝҷдёӘзӯ”жЎҲи§ЈеҶізҡ„й—®йўҳдёҺй—®йўҳжүҖй—®зҡ„дёҚеҗҢгҖӮй—®йўҳжҳҜеҰӮдҪ•еңЁеҸҚеәҸеҲ—еҢ–жңҹй—ҙд»ҺJSONдёӯиҺ·еҸ–$idеҖјгҖӮиҖҢдҪ зҡ„зӯ”жЎҲеҲҷиҜҙжҳҺдәҶеҰӮдҪ•еңЁеәҸеҲ—еҢ–жңҹй—ҙеҲ йҷӨ$idеҖјгҖӮ - Brian Rogers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接