当前位置: 首页 > 面试题库 >

Null推论运算符为动态对象的属性返回null

颛孙安康
2023-03-14
问题内容

我最近发现在使用Json.NET将JSON解析为动态对象时,使用null-coalescing运算符存在问题。假设这是我的动态对象:

string json = "{ \"phones\": { \"personal\": null }, \"birthday\": null }";
dynamic d = JsonConvert.DeserializeObject(json);

如果我尝试使用?? d字段之一上的运算符,它返回null:

string s = "";
s += (d.phones.personal ?? "default");
Console.WriteLine(s + " " + s.Length); //outputs  0

但是,如果我将动态属性分配给字符串,则可以正常工作:

string ss = d.phones.personal;
string s = "";
s += (ss ?? "default");
Console.WriteLine(s + " " + s.Length); //outputs default 7

最后,当我输出Console.WriteLine(d.phones.personal == null)时输出True

我已经在Pastebin上对这些问题进行了广泛的测试。


问题答案:

这是由于Json.NET和??运算符的晦涩行为。

首先,当您将JSON反序列化为dynamic对象时,实际返回的是Linq-to-
JSON类型的子类JToken(例如JObjectJValue),该子类具有的自定义实现IDynamicMetaObjectProvider。即

dynamic d1 = JsonConvert.DeserializeObject(json);
var d2 = JsonConvert.DeserializeObject<JObject>(json);

实际上正在返回同一件事。因此,对于您的JSON字符串,如果我愿意

    var s1 = JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"];
    var s2 = JsonConvert.DeserializeObject<dynamic>(json).phones.personal;

这两个表达式的求值结果都是完全相同的返回动态对象。但是返回什么对象?这就使我们向Json.NET的第二晦涩的行为:而不是代表空值null指针,它具有特殊的代表然后JValueJValue.Type等于JTokenType.Null。因此,如果我这样做:

    WriteTypeAndValue(s1, "s1");
    WriteTypeAndValue(s2, "s2");

控制台输出为:

"s1":  Newtonsoft.Json.Linq.JValue: ""
"s2":  Newtonsoft.Json.Linq.JValue: ""

即,这些对象 不为null ,它们被分配了POCO,并且它们ToString()返回空字符串。

但是,当我们将动态类型分配给字符串时会发生什么呢?

    string tmp;
    WriteTypeAndValue(tmp = s2, "tmp = s2");

印刷品:

"tmp = s2":  System.String: null value

为什么会有所不同?这是因为DynamicMetaObject通过返回JValue来解决动态类型的字符串最终调用转换ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type),最终收益nullJTokenType.Null价值,这是同样的逻辑进行利用显式的字符串避免的所有用途dynamic

    WriteTypeAndValue((string)JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"], "Linq-to-JSON with cast");
    // Prints "Linq-to-JSON with cast":  System.String: null value

    WriteTypeAndValue(JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"], "Linq-to-JSON without cast");     
    // Prints "Linq-to-JSON without cast":  Newtonsoft.Json.Linq.JValue: ""

现在,到实际的问题。正如赫斯特克所说的那样?dynamic当两个操作数之一为时dynamic,operator返回,因此d.phones.personal ?? "default"不尝试执行类型转换,因此返回为JValue

    dynamic d = JsonConvert.DeserializeObject<dynamic>(json);
    WriteTypeAndValue((d.phones.personal ?? "default"), "d.phones.personal ?? \"default\"");
    // Prints "(d.phones.personal ?? "default")":  Newtonsoft.Json.Linq.JValue: ""

但是,如果我们通过将动态返回值分配给字符串来将Json.NET的类型转换调用为字符串,则转换器将在
_合并运算符完成其工作并返回非null之后JValue_返回并返回实际的空指针:

    string tmp;
    WriteTypeAndValue(tmp = (d.phones.personal ?? "default"), "tmp = (d.phones.personal ?? \"default\")");
    // Prints "tmp = (d.phones.personal ?? "default")":  System.String: null value

这解释了您所看到的差异。

为避免此行为,请在应用合并运算符之前强制从动态转换为字符串:

s += ((string)d.phones.personal ?? "default");

最后,使用辅助方法将类型和值写入控制台:

public static void WriteTypeAndValue<T>(T value, string prefix = null)
{
    prefix = string.IsNullOrEmpty(prefix) ? null : "\""+prefix+"\": ";

    Type type;
    try
    {
        type = value.GetType();
    }
    catch (NullReferenceException)
    {
        Console.WriteLine(string.Format("{0} {1}: null value", prefix, typeof(T).FullName));
        return;
    }
    Console.WriteLine(string.Format("{0} {1}: \"{2}\"", prefix, type.FullName, value));
}

(顺便说一句,null类型的存在JValue解释了表达式(object)(JValue)(string)null == (object)(JValue)null可能如何求和false。)



 类似资料:
  • 我有以下JPQL查询: 这应该返回所有对具有NULL的。 但是,我正在运行代码并进行调试,我看到返回的集合包含此字段为null的实体。 这是JPQL中的错误吗?

  • 我们有一个基于泽西的web应用程序。我们将Apache Freemarker用于前端网页。我已经创建了一个servlet过滤器,并将其添加到web.xml中。这个过滤器拦截所有的http请求。在该过滤器的doFilter方法中,我从请求对象获取HTTPSession并设置一个属性值。说“Myval”。 尝试了以下访问MyVal的方法: ${request.myval} ${myVal}

  • 如果(tagrecord.gettagid()==null&&tagrecord.gettaglabel()==null),我实际上希望生成的方法返回一个null标记对象。有没有可能,我该如何实现这一点?

  • 在单元测试(Groovy和Spock)的中,我用mock填充上面的字段: 接下来,在我的测试用例中,我调用一个方法,我希望从中获取,该方法来自步骤中的模拟对象: 问题是,对于,我将获得。 这是因为我以错误的方式使用Groovy吗?如何使成为它应该成为的对象?我应该使用mock/stub吗?

  • 问题内容: 嗨:在我们的应用程序中,我们已经从数据库中检索了一些数据,例如,表中包含以下字段:id,名称,年龄,地址,电子邮件。 然后,我们将根据客户提供一些这些属性。 如果客户需要ID,名称,我们将获得ID名称;如果客户需要ID,名称,年龄,则将获得ID,名称,年龄。 现在,我们想创建一个包装这些属性的类。但是,我们不知道确切要求哪个字段。 我可以在这里用Class替换地图吗? 问题答案: 如果

  • 问题内容: 我如何做到这一点: 问题答案: 请改用方括号表示法。 或者,在现代JavaScript中: