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

问题描述

最近我在使用Json.NET解析JSON作为动态对象时,最近发现了一个null-coalescing操作符的问题。假设这是我的动态对象:

  c 



我已经做了对上的这些问题的广泛测试。

解决方案

这是由于Json.NET和 ?? / code>运算符的模糊行为。



当您将JSON反序列化为动态对象时,实际返回的是Linq-to-JSON类型的子类 JToken (例如或),其定制实现为。 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 指针指向空值,它代表着一个特殊的 等于。因此,如果我这样做:

  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 

为什么有区别?这是因为由将动态类型转换为字符串,最终调用,最终为 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:

现在,到实际的问题。由于注意到返回动态,其中两个操作数之一为 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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-16 00:46