空 - 合并运算符为动态对象的属性返回null [英] Null-coalescing operator returning null for properties of dynamic objects
问题描述
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); //输出0
但是,如果我为动态属性分配一个字符串,那么它工作正常:
string ss = d.phones.personal;
string s =;
s + =(ss ??default);
Console.WriteLine(s ++ s.Length); //输出默认值7
最后,当我输出 Console.WriteLine
c
我已经做了对 Pastebin 上的这些问题的广泛测试。
这是由于Json.NET和 ?? / code>运算符的模糊行为。
当您将JSON反序列化为动态
对象时,实际返回的是Linq-to-JSON类型的子类 JToken
(例如 JObject
或 JValue
),其定制实现为 IDynamicMetaObjectProvider
。 i.e。
dynamic d1 = JsonConvert.DeserializeObject(json);
var d2 = JsonConvert.DeserializeObject< JObject>(json);
实际上返回相同的事情。所以,对于你的JSON字符串,如果我执行
var s1 = JsonConvert.DeserializeObject< JObject>(json)[电话 ][个人];
var s2 = JsonConvert.DeserializeObject< dynamic>(json).phones.personal;
这两个表达式都计算出完全相同的返回的动态对象。但是什么对象被返回?这让我们得到了Json.NET的第二个晦涩的行为:而不是用 null
指针指向空值,它代表着一个特殊的 JValue
JValue.Type
等于 JTokenType.Null
。因此,如果我这样做:
WriteTypeAndValue(s1,s1);
WriteTypeAndValue(s2,s2);
控制台输出为:
<$ p $
s2:Newtonsoft.Json.Linq.JValue:
这些对象是 not null ,它们被分配了POCO,而他们的 ToString()
返回一个空字符串。
但是,当我们将动态类型分配给字符串时会发生什么?
string tmp;
WriteTypeAndValue(tmp = s2,tmp = s2);
打印:
code>tmp = s2:System.String:null value
为什么有区别?这是因为 DynamicMetaObject
由 JValue
将动态类型转换为字符串,最终调用 ConvertUtils.Convert(value,CultureInfo.InvariantCulture,binder.Type)
,最终为 JTokenType.Null
值返回 null
,这是
WriteTypeAndValue((string)JsonConvert.DeserializeObject< JObject>(json)[phones] [personal],Linq-to-JSON with cast);
//打印Linq-to-JSON with cast:System.String:null value
WriteTypeAndValue(JsonConvert.DeserializeObject< JObject>(json)[phones] [personal ],Linq-to-JSON without cast);
//打印Linq-to-JSON without cast:Newtonsoft.Json.Linq.JValue:
现在,到实际的问题。由于 husterk 注意到 ??运算符返回动态
,其中两个操作数之一为 dynamic
,所以 d电话人default
不会尝试执行类型转换,因此返回是一个 JValue
:
dynamic d = JsonConvert.DeserializeObject< dynamic>(json);
WriteTypeAndValue((d.phones.personal ??default),d.phones.personal?\default\);
//打印(d.phones.personal ??default):Newtonsoft.Json.Linq.JValue:
但是,如果我们通过将动态返回值分配给字符串来调用Json.NET的类型转换为字符串,则转换器将在合并运算符之后启动并返回一个实际的空指针已经完成了工作并返回了一个非空的 JValue
:
string tmp;
WriteTypeAndValue(tmp =(d.phones.personal ??default),tmp =(d.phones.personal?\default\));
//打印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 )? null:\+前缀+\:;
类型;
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
)。
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);
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
Finally, when I output Console.WriteLine(d.phones.personal == null)
it outputs True
.
I have made an extensive test of these issues on Pastebin.
This is due to obscure behaviors of Json.NET and the ??
operator.
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);
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;
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: ""
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
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: ""
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: ""
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));
}
(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屋!