Это происходит из-за неясных поведения Json.NET и оператора ??
.
Во-первых, когда вы десериализации JSON к dynamic
объекта, что на самом деле возвращается в подкласс Linq-на-JSON типа JToken
(например, JObject
или JValue
), который имеет пользовательский реализации IDynamicMetaObjectProvider
. То есть
dynamic d1 = JsonConvert.DeserializeObject(json);
var d2 = JsonConvert.DeserializeObject<JObject>(json);
Фактически возвращаются то же самое. Итак, для вашей строки JSON, если я делаю
var s1 = JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"];
var s2 = JsonConvert.DeserializeObject<dynamic>(json).phones.personal;
Оба эти выражения оцениваются точно таким же возвращенным динамическим объектом. Но какой объект возвращается? Это подводит нас к второму неясному поведению Джсона.NET: вместо представления нулевых значений с помощью указателей null
, он представляет собой со значением JValue
с JValue.Type
, равным JTokenType.Null
. Таким образом, если я:
WriteTypeAndValue(s1, "s1");
WriteTypeAndValue(s2, "s2");
Выход консоли:
"s1": Newtonsoft.Json.Linq.JValue: ""
"s2": Newtonsoft.Json.Linq.JValue: ""
Т.е. эти объекты: не null, они выделяются POCOs, а их ToString()
возвращает пустую строку.
Но что происходит, когда мы назначаем этот динамический тип строке?
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
:
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 отметил ?? operator возвращает dynamic
, когда один из двух операндов 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: ""
Но если мы вызываем преобразование типа Json.NET в в строку путем присвоения динамического возврата в строку, то преобразователь будет удар и возвращает реальный указатель нулевого после оператора коалесцирующего выполнил свою работу и вернулся ненулевой JValue
:
string tmp;
WriteTypeAndValue(tmp = (d.phones.personal ?? "default"), "tmp = (d.phones.personal ?? \"default\")");
// Prints "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(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
).
Можете ли вы немного упростить это? Удалите вложенные объекты, длину и '+ ='. Я сомневаюсь, что они должны воспроизвести проблему. – usr
Я пробовал смотреть на ИЛ, но смотреть на ИЛ вокруг «динамика» никогда не бывает удовольствием :) – MarcinJuraszek