Jam*_* Ko 12 c# json dynamic json.net null-coalescing-operator
我最近在使用Json.NET将JSON解析为动态对象时发现了null-coalescing运算符的问题.假设这是我的动态对象:
string json = "{ \"phones\": { \"personal\": null }, \"birthday\": null }";
dynamic d = JsonConvert.DeserializeObject(json);
Run Code Online (Sandbox Code Playgroud)
如果我尝试使用?? 运算符在d的一个字段上,它返回null:
string s = "";
s += (d.phones.personal ?? "default");
Console.WriteLine(s + " " + s.Length); //outputs 0
Run Code Online (Sandbox Code Playgroud)
但是,如果我将动态属性分配给字符串,那么它工作正常:
string ss = d.phones.personal;
string s = "";
s += (ss ?? "default");
Console.WriteLine(s + " " + s.Length); //outputs default 7
Run Code Online (Sandbox Code Playgroud)
最后,当我输出Console.WriteLine(d.phones.personal == null)它时True.
我已经在Pastebin上对这些问题进行了广泛的测试.
dbc*_*dbc 21
这是由于Json.NET和??运营商的模糊行为造成的.
首先,当您将JSON反序列化为dynamic对象时,实际返回的是Linq-to-JSON类型的子类JToken(例如JObject或者JValue),其具有自定义实现IDynamicMetaObjectProvider.即
dynamic d1 = JsonConvert.DeserializeObject(json);
var d2 = JsonConvert.DeserializeObject<JObject>(json);
Run Code Online (Sandbox Code Playgroud)
实际上是回归同样的事情.所以,对于你的JSON字符串,如果我这样做
var s1 = JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"];
var s2 = JsonConvert.DeserializeObject<dynamic>(json).phones.personal;
Run Code Online (Sandbox Code Playgroud)
这两个表达式都评估完全相同的返回动态对象.但是返回了什么对象?这让我们看到了Json.NET的第二个模糊行为:它不是用null指针表示空值,而是JValue用JValue.Type等于的特殊代表JTokenType.Null.因此,如果我这样做:
WriteTypeAndValue(s1, "s1");
WriteTypeAndValue(s2, "s2");
Run Code Online (Sandbox Code Playgroud)
控制台输出是:
"s1": Newtonsoft.Json.Linq.JValue: ""
"s2": Newtonsoft.Json.Linq.JValue: ""
Run Code Online (Sandbox Code Playgroud)
即这些对象不为空,它们被分配POCO,并且它们ToString()返回一个空字符串.
但是,当我们将该动态类型分配给字符串时会发生什么?
string tmp;
WriteTypeAndValue(tmp = s2, "tmp = s2");
Run Code Online (Sandbox Code Playgroud)
打印:
"tmp = s2": System.String: null value
Run Code Online (Sandbox Code Playgroud)
为什么不同?这是因为DynamicMetaObject返回by JValue来解析动态类型到字符串的转换最终会调用ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type)最终返回null的JTokenType.Null值,这与显式强制转换为字符串执行的逻辑相同,从而避免了以下所有用法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: ""
Run Code Online (Sandbox Code Playgroud)
现在,到实际的问题.正如赫斯特克注意到的那样?dynamic当两个操作数之一出现时,operator返回dynamic,因此d.phones.personal ?? "default"不会尝试执行类型转换,因此返回的是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: ""
Run Code Online (Sandbox Code Playgroud)
但是如果我们通过将动态返回分配给字符串来调用Json.NET的类型转换为字符串,那么转换器将在合并运算符完成其工作并返回非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
Run Code Online (Sandbox Code Playgroud)
这解释了您所看到的差异.
要避免此行为,请在应用合并运算符之前强制将转换从动态转换为字符串:
s += ((string)d.phones.personal ?? "default");
Run Code Online (Sandbox Code Playgroud)
最后,将类型和值写入控制台的辅助方法:
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));
}
Run Code Online (Sandbox Code Playgroud)
(另外,null类型的存在JValue解释了表达式(object)(JValue)(string)null == (object)(JValue)null可能如何评估false).
| 归档时间: |
|
| 查看次数: |
1755 次 |
| 最近记录: |