空合并运算符的动态对象的属性返回null [英] Null-coalescing operator returning null for properties of dynamic objects

查看:597
本文介绍了空合并运算符的动态对象的属性返回null的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近发现的空合并运算符的一个问题,而使用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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆