Ciò è dovuto al oscuri comportamenti Json.NET e l'operatore ??
.
primo luogo, quando si deserializzare JSON a un oggetto dynamic
, di fatto viene restituito è una sottoclasse della Linq to JSON tipo JToken
(ad esempio JObject
o JValue
) che ha un'implementazione personalizzata IDynamicMetaObjectProvider
. Cioè
dynamic d1 = JsonConvert.DeserializeObject(json);
var d2 = JsonConvert.DeserializeObject<JObject>(json);
In realtà stanno restituendo la stessa cosa. Quindi, per la stringa JSON, se lo faccio
var s1 = JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"];
var s2 = JsonConvert.DeserializeObject<dynamic>(json).phones.personal;
Entrambe queste espressioni valutano esattamente lo stesso oggetto dinamico restituito. Ma quale oggetto viene restituito? Questo ci porta al secondo oscuro comportamento di Json.NET: anziché rappresentare valori nulli con i puntatori null
, rappresenta quindi con uno specialecon JValue.Type
uguale a JTokenType.Null
. Così se faccio:
WriteTypeAndValue(s1, "s1");
WriteTypeAndValue(s2, "s2");
L'uscita della console è:
"s1": Newtonsoft.Json.Linq.JValue: ""
"s2": Newtonsoft.Json.Linq.JValue: ""
Vale a dire questi oggetti sono non null, vengono assegnati POCO e il loro ToString()
restituisce una stringa vuota.
Ma, cosa succede quando assegniamo quel tipo dinamico a una stringa?
string tmp;
WriteTypeAndValue(tmp = s2, "tmp = s2");
Stampe:
"tmp = s2": System.String: null value
Perché la differenza? È perché il DynamicMetaObject
restituito da JValue
per risolvere la conversione di tipo dinamico a stringa casualmente chiamate ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type)
che alla fine restituisce null
per un valore JTokenType.Null
, che è la stessa logica eseguita dal cast esplicito a stringa evitando tutti gli usi di 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: ""
Ora, alla domanda reale. Come osservato husterk i rendimenti ?? operatordynamic
quando uno dei due operandi è dynamic
, quindi d.phones.personal ?? "default"
non tenta di eseguire una conversione di tipo, quindi il ritorno è un 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: ""
Ma se si invoca conversione del tipo di Json.NET a stringa assegnando il ritorno dinamica di una stringa, il convertitore entreranno in e restituire un puntatore nullo effettivo dopo che l'operatore coalescenza ha svolto il proprio lavoro e restituito un non nullo JValue
:
string tmp;
WriteTypeAndValue(tmp = (d.phones.personal ?? "default"), "tmp = (d.phones.personal ?? \"default\")");
// Prints "tmp = (d.phones.personal ?? "default")": System.String: null value
Questo spiega la differenza che si sta vedendo.
Per evitare questo problema, forzare la conversione da dinamico a stringa prima viene applicato l'operatore coalescenza:
s += ((string)d.phones.personal ?? "default");
Infine, il metodo di supporto per scrivere il tipo e il valore alla 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));
}
(Per inciso, l'esistenza del tipo nullo JValue
spiega come l'espressione (object)(JValue)(string)null == (object)(JValue)null
potrebbe valutare su false
).
Potete semplificarlo un po '? Rimuovi gli oggetti nidificati, la lunghezza e '+ ='. Dubito che siano tenuti a riprodurre il problema. – usr
Ho provato a guardare IL, ma guardare IL attorno a 'dynamic' non è mai un piacere :) – MarcinJuraszek