問題描述
我最近在使用 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
的子類(例如 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 的第二個模糊行為:不是用 null
指針表示空值,而是用一個特殊的 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");
控制臺輸出為:
"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
為什么不一樣?這是因為 DynamicMetaObject
由 JValue
解決動態類型到字符串的轉換最終調用 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 的空合并運算符的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!