Wynika to z niejasnych zachowań Json.NET i operatora ??
.
Po pierwsze, kiedy deserializować JSON do dynamic
obiektu, co jest rzeczywiście zwrócony jest podklasą LINQ-JSON typu JToken
(np JObject
lub JValue
), która ma niestandardową implementację IDynamicMetaObjectProvider
. To znaczy.
dynamic d1 = JsonConvert.DeserializeObject(json);
var d2 = JsonConvert.DeserializeObject<JObject>(json);
Rzeczywiście wracają to samo. Tak więc, dla twojego ciągu JSON, jeśli wykonuję
var s1 = JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"];
var s2 = JsonConvert.DeserializeObject<dynamic>(json).phones.personal;
Oba te wyrażenia oceniają dokładnie ten sam zwracany obiekt dynamiczny. Ale jaki przedmiot jest zwracany? To prowadzi nas do drugiego, niejasnego zachowania Jsona.NET: zamiast reprezentowania wartości pustych ze wskaźnikami
null
reprezentuje wtedy specjalny
JValue
z
JValue.Type
równy
JTokenType.Null
. Tak więc jeśli robię:
WriteTypeAndValue(s1, "s1");
WriteTypeAndValue(s2, "s2");
Wyjście konsola jest:
"s1": Newtonsoft.Json.Linq.JValue: ""
"s2": Newtonsoft.Json.Linq.JValue: ""
Tj obiekty te są nie puste, przydzielane są POCO, a ich ToString()
zwraca pusty ciąg znaków.
Ale co się stanie, gdy przypiszemy ten typ dynamiczny do łańcucha?
string tmp;
WriteTypeAndValue(tmp = s2, "tmp = s2");
Wydruki:
"tmp = s2": System.String: null value
Dlaczego różnica? To dlatego, że DynamicMetaObject
zwrócony przez JValue
rozwiązać konwersję dynamicznego typu STRING ostatecznie nazywa ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type)
który ostatecznie zwraca null
dla wartości JTokenType.Null
, która jest taka sama logika wykonywana przez wyraźnej obsady ciąg unikając wszystkie zastosowania 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: ""
Teraz, na rzeczywiste pytanie. Jak husterk odnotowała ?? operator Przywraca dynamic
gdy jeden z dwóch argumentów jest dynamic
, więc d.phones.personal ?? "default"
nie próbuje wykonać konwersję typu, a więc powrót jest 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: ""
Ale jeśli wzywamy Typ konwersji Json.NET za ciąg przypisując dynamiczny powrót na sznurku, a następnie konwerter będzie kopać i powrócić rzeczywisty wskaźnik NULL po operatora koalescencyjnego wykonał swoją pracę i wrócił niepusta JValue
:
string tmp;
WriteTypeAndValue(tmp = (d.phones.personal ?? "default"), "tmp = (d.phones.personal ?? \"default\")");
// Prints "tmp = (d.phones.personal ?? "default")": System.String: null value
To wyjaśnia różnicę, którą widzisz.
Aby uniknąć tego problemu, należy wymusić konwersję z dynamiczny ciąg zanim operator koalescencyjny jest stosowana:
s += ((string)d.phones.personal ?? "default");
Wreszcie metoda pomocnika napisać rodzaj i wartość do konsoli:
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));
}
(Na marginesie, istnienie typu zerowego JValue
wyjaśnia, w jaki sposób wyrażenie (object)(JValue)(string)null == (object)(JValue)null
może oszacować na false
).
Czy możesz to nieco uprościć? Usuń zagnieżdżone obiekty, długość i '+ ='. Wątpię, czy są oni zobowiązani do odtworzenia problemu. – usr
Próbowałem patrzeć na IL, ale patrzenie na IL wokół 'dynamiki 'nigdy nie jest przyjemnością :) – MarcinJuraszek