【问题标题】:JsonConvert.DeserializeObject<> (string) returns null value for $id propertyJsonConvert.DeserializeObject<> (string) 为 $id 属性返回空值
【发布时间】:2026-02-20 17:15:01
【问题描述】:

我正在使用 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) 属性始终为空。起初我试图弄清楚我从服务器返回的美元符号是否不同,但事实并非如此。我不知道从这里去哪里,有什么想法吗?

提前致谢。

注意:如果我尝试使用 JavaScriptSerializer 之类的东西进行反序列化,它可以完美运行,所以我相当确定我的模型或 JSON.net 有问题。不过可能是错的。

【问题讨论】:

  • 尝试不使用$。我建议不要使用特殊字符作为键。
  • @BradM 感谢您的建议。不幸的是,这不是一个选项,因为 Api 不在我的控制范围内。虽然它可能不被推荐,但它经常发生并且不应该是这个问题的原因。还有其他想法吗?

标签: c# json.net webclient


【解决方案1】:

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\""));
}

但是,由于您在 cmets 中说您不太热衷于使用字符串替换的想法,因此我研究了其他选项。我确实找到了另一种可能对您有用的替代方案——自定义JsonConverter。转换器背后的想法是,它会尝试使用 Json.Net 的内置反序列化机制来创建和填充对象(无 ID),然后从 JSON 中手动检索 $id 属性并使用它来填充 @987654335 @ 对象上的属性通过反射。

这是转换器的代码:

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&lt;T&gt;,如下所示:

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

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

让我知道这是否适合你。

【讨论】:

  • 感谢您付出额外的努力,Brian。我测试了你的转换器,它就像一个魅力。尽管prop.SetValue(o, id.ToString()); 确实存在语法问题,但我需要将null 作为最后的第三个参数来构建它。这对你有意义吗?
  • 我猜你一定在使用旧版本的 .Net 框架(我使用的是 4.5)。 SetValue 的第三个参数用于防止属性碰巧是一个索引器,而这里不是。所以是的,在第三个参数中传递 null 以使其工作是有道理的,而且是正确的。
【解决方案2】:

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

从 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 应该完全允许 ​​$ :*.com/questions/4638585/…。使用 String.replace 显然不是一个好选择,因为我的响应正文中可能有我想保留的 $ 字符。
  • @itslittlejohn 问题不在于$ 本身,而在于整个$id。 Json.Net 使用这种表示法(以及$ref)来保存 JSON 中的对象引用。因此,当它看到 $id 时,它假定该属性不是实际 JSON 属性集的一部分,而是一个内部标识符。在反序列化之前用"id"(包括引号字符)替换"$id" 的字符串可能是一种可行的解决方法。此字符序列不太可能出现在您的实际响应正文中的其他位置。如果我有更多时间,我会更多地研究这个问题,看看是否有更好的解决方案。
  • @BrianRogers 谢谢布赖恩。如果您知道处理此问题的好方法,我将不胜感激。我想字符串替换会起作用,但我不想对来自服务器的每个响应都这样做。
【解决方案3】:

这个答案为我解决了 $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。

【讨论】:

  • 此答案解决的问题与问题中提出的问题不同。问题是如何在反序列化过程中从 JSON 中获取 $id 值。您的回答说明了如何在序列化过程中删除 $id 值。