空合并运算符的动态对象的属性返回null [英] Null-coalescing operator returning null for properties of dynamic objects
问题描述
我最近发现的空合并运算符的一个问题,而使用Json.NET解析JSON作为动态对象。假设这是我的动态对象:
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)
它输出真
。
我已经这些问题在引擎收录了广泛的测试。
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到动态
对象,什么是真正返回的是Linq到JSON类型的子类 JToken
(如 JObject
或 JValue
),其具有的 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的第二个不起眼的行为:而不是空
指针表示空值,它具有特殊的 JValue
与 JValue.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");
控制台输出是:
The console output is:
"s1": Newtonsoft.Json.Linq.JValue: ""
"s2": Newtonsoft.Json.Linq.JValue: ""
即这些对象的不为空的,他们被分配波苏斯,他们的的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");
打印:
Prints:
"tmp = s2": System.String: null value
为什么会有差别?这是因为 DynamicMetaObject
通过的 JValue
来解决动态类型为字符串转换最终调用的 ConvertUtils.Convert(价值,CultureInfo.InvariantCulture,binder.Type)
最终返回空
为 JTokenType.Null
价值,这是通过显式转换为字符串避免动态的所有用途进行同样的逻辑
:
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 指出, ?运营商返回动态
当两个操作数是动态
,所以 D .phones.personal? 默认
不会尝试进行类型转换,从而回报是一个 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的类型转换为字符串通过分配动态返回一个字符串,然后转换器将踢和合并运算符后返回一个实际的空指针的已完成其工作,并返回一个非空 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
解释如何表达(目标)(JValue)(字符串)空==(对象)(JValue)空
有可能会评估为假
)。
(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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!