为动态对象的属性返回 null 的空合并运算符

Null-coalescing operator returning null for properties of dynamic objects(为动态对象的属性返回 null 的空合并运算符)
本文介绍了为动态对象的属性返回 null 的空合并运算符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

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

I have recently found a problem with the null-coalescing operator while using Json.NET to parse JSON as dynamic objects. Suppose this is my dynamic object:

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

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

If I try to use the ?? operator on one of the field of d, it returns null:

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

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

However, if I assign a the dynamic property to a string, then it works fine:

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.

Finally, when I output Console.WriteLine(d.phones.personal == null) it outputs True.

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

I have made an extensive test of these issues on Pastebin.

推荐答案

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

This is due to obscure behaviors of Json.NET and the ?? operator.

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

Firstly, when you deserialize JSON to a dynamic object, what is actually returned is a subclass of the Linq-to-JSON type JToken (e.g. JObject or JValue) which has a custom implementation of IDynamicMetaObjectProvider. I.e.

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

实际上返回的是相同的东西.所以,对于你的 JSON 字符串,如果我这样做了

Are actually returning the same thing. So, for your JSON string, if I do

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

这两个表达式都返回完全相同的动态对象.但是返回的是什么对象?这让我们看到了 Json.NET 的第二个模糊行为:不是用 null 指针表示空值,而是用一个特殊的 JValueJValue.Type 等于 JTokenType.Null.因此,如果我这样做:

Both these expressions evaluate to exactly the same returned dynamic object. But what object is returned? That gets us to the second obscure behavior of Json.NET: rather than representing null values with null pointers, it represents then with a special JValue with JValue.Type equal to JTokenType.Null. Thus if I do:

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

控制台输出为:

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

即这些对象不为空,它们被分配了 POCO,它们的 ToString() 返回一个空字符串.

I.e. these objects are not null, they are allocated POCOs, and their ToString() returns an empty string.

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

But, what happens when we assign that dynamic type to a string?

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

打印:

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

为什么不一样?这是因为 DynamicMetaObjectJValue 解决动态类型到字符串的转换最终调用 ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type) 最终返回 null 用于 JTokenType.Null 值,这与显式转换为字符串执行的逻辑相同,避免所有使用 dynamic:

Why the difference? It is because the DynamicMetaObject returned by JValue to resolve the conversion of the dynamic type to string eventually calls ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type) which eventually returns null for a JTokenType.Null value, which is the same logic performed by the explicit cast to string avoiding all uses of 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: ""

现在,进入实际问题.正如 husterk 所指出的??当两个操作数之一是 dynamic 时,operator 返回 dynamic,所以 d.phones.personal ??"default" 不会尝试执行类型转换,因此返回的是 JValue:

Now, to the actual question. As husterk noted the ?? operator returns dynamic when one of the two operands is dynamic, so d.phones.personal ?? "default" does not attempt to perform a type conversion, thus the return is a 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:

But if we invoke Json.NET's type conversion to string by assigning the dynamic return to a string, then the converter will kick in and return an actual null pointer after the coalescing operator has done its work and returned a non-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

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

This explains the difference you are seeing.

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

To avoid this behavior, force the conversion from dynamic to string before the coalescing operator is applied:

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

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

Finally, the helper method to write the type and value to the console:

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

(顺便说一句,空类型 JValue 的存在解释了表达式 (object)(JValue)(string)null == (object)(JValue)null 可能评估为 false).

(As an aside, the existence of the null-type JValue explains how the expression (object)(JValue)(string)null == (object)(JValue)null might possibly evaluate to false).

这篇关于为动态对象的属性返回 null 的空合并运算符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

本站部分内容来源互联网,如果有图片或者内容侵犯了您的权益,请联系我们,我们会在确认后第一时间进行删除!

相关文档推荐

Force JsonConvert.SerializeXmlNode to serialize node value as an Integer or a Boolean(强制 JsonConvert.SerializeXmlNode 将节点值序列化为整数或布尔值)
Using JSON to Serialize/Deserialize TimeSpan(使用 JSON 序列化/反序列化 TimeSpan)
Could not determine JSON object type for type quot;Classquot;(无法确定类型“Class的 JSON 对象类型.)
How to deserialize a JSONP response (preferably with JsonTextReader and not a string)?(如何反序列化 JSONP 响应(最好使用 JsonTextReader 而不是字符串)?)
how to de-serialize JSON data in which Timestamp it-self contains fields?(如何反序列化时间戳本身包含字段的JSON数据?)
JSON.Net custom contract serialization and Collections(JSON.Net 自定义合约序列化和集合)