JsonConvert.DeserializeObject (cadena) devuelve valor nulo para la propiedad $ id

Estoy descargando el JSON usando System.Net.WebClient.DownloadString. Estoy obteniendo una respuesta válida:

{ "FormDefinition": [ { "$id":"4", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Punchworks Form" }, { "$id":"6", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Punchworks Form test second" }, { "$id":"46", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"any_Name" }, { "$id":"47", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Punchworks Form test second" }, { "$id":"49", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Testing Name ??´????? ???? ACEeiÅ¡uu { [ ( ~ ! @ # " }, { "$id":"50", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"something new" }, { "$id":"56", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Testing Name руÌÑÑкий 汉è¯æ¼¢èªž ĄČĘėįšųū { [ ( ~ ! @ # " }, { "$id":"57", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Test Name" }, { "$id":"58", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 12:59:29 PM" }, { "$id":"59", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:01:18 PM" }, { "$id":"60", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:40:44 PM" }, { "$id":"61", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:43:46 PM" }, { "$id":"62", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:48:21 PM" }, { "$id":"63", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:57:00 PM" }, { "$id":"64", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:57:53 PM" }, { "$id":"65", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Unique Name - 12/16/2013 1:58:46 PM" }, { "$id":"79", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Testing Name1211" }, { "$id":"80", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Testing Name1211" }, { "$id":"81", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"any_nami" }, { "$id":"90", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Test_something3" }, { "$id":"91", "Class":558, "ClassDisplayLabel":"Punchworks", "Name":"Test_something4" }] } 

Y aquí está mi Modelo:

 public class FormDefinitionList { [JsonProperty("FormDefinition")] public List FormDefinitions { get; set; } } public class FormDefinition { [JsonProperty ("$id")] public string Id { get; set; } [JsonProperty ("Class")] public int Class { get; set; } [JsonProperty ("ClassName")] public string ClassName { get; set; } [JsonProperty ("ClassDisplayLabel")] public string ClassDisplayLabel { get; set; } [JsonProperty ("Definition")] public string Definition { get; set; } [JsonProperty ("Name")] public string Name { get; set; } } 

Todo funciona cuando lo hago:

 string response = "json as above"; FormDefinitionList root = JsonConvert.DeserializeObject (response); 

excepto que la propiedad Id ($ id) siempre es nula. Al principio traté de averiguar si el símbolo de signo de dólar que estaba recibiendo del servidor era diferente, pero ese no parece ser el caso. No estoy seguro de dónde ir desde aquí, entonces ¿alguna idea?

Gracias por adelantado.

NOTA: Si trato de deserializar con algo como JavaScriptSerializer, funciona perfectamente, así que estoy bastante seguro de que algo anda mal con mi modelo o con JSON.net. Sin embargo, podría estar equivocado.

Json.Net normalmente usa $id junto con $ref como metadatos para conservar referencias de objetos en JSON. Entonces, cuando ve $id , asume que la propiedad no es parte del conjunto de propiedades JSON real, sino un identificador interno. Por lo tanto, no llena la propiedad Id en su objeto, aunque haya incluido un atributo [JsonProperty] que indique que debería.

ACTUALIZAR

A partir de la versión 6.0.4 de Json.Net , existe una nueva configuración mediante la cual puede indicar al deserializador que trate estas propiedades de “metadatos” como propiedades normales en lugar de consumirlas. Todo lo que necesita hacer es establecer la configuración de MetadataPropertyHandling en Ignore y luego deserializar como de costumbre.

 var settings = new JsonSerializerSettings(); settings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; var obj = JsonConvert.DeserializeObject(json, settings); 

Antes de la versión 6.0.4, se necesitaba una solución para resolver este problema. El rest de esta respuesta analiza las posibles soluciones provisionales. Si usa 6.0.4 o posterior, no necesita la solución alternativa y puede dejar de leer ahora.


La solución más simple que puedo ver es hacer un reemplazo de cadena de "$id" con "id" (incluidas las comillas) en el JSON antes de deserializarlo, como sugirió @Carlos Coelho. Como tendrías que hacer esto con cada respuesta, si sigues esta ruta, te recomendaría hacer un método de ayuda simple para evitar la duplicación del código, por ejemplo:

 public static T Deserialize(string json) { return JsonConvert.DeserializeObject(json.Replace("\"$id\"", "\"id\"")); } 

Sin embargo, como dijo en sus comentarios que no le gusta tanto la idea de usar una cadena de reemplazo, busqué otras opciones. Encontré otra alternativa que podría funcionar para JsonConverter un JsonConverter personalizado. La idea detrás del convertidor es que intentaría usar los mecanismos de deserialización integrados de Json.Net para crear y poblar el objeto (sin ID), luego recuperar manualmente la propiedad $id del JSON y usarla para completar la propiedad Id en el objeto a través de la reflexión.

Aquí está el código para el convertidor:

 public class DollarIdPreservingConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(FormDefinition); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); object o = jo.ToObject(objectType); JToken id = jo["$id"]; if (id != null) { PropertyInfo prop = objectType.GetProperty("Id"); if (prop != null && prop.CanWrite && prop.PropertyType == typeof(string)) { prop.SetValue(o, id.ToString(), null); } } return o; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } 

Traté de escribir el convertidor de modo que funcionara para cualquier objeto que tenga el $id ; solo necesita cambiar el método CanConvert consecuencia para que sea verdadero para todos los tipos para los que necesita usarlo además de FormDefinition .

Para utilizar el convertidor, solo tiene que pasar una instancia del mismo a DeserializeObject esta manera:

 FormDefinitionList root = JsonConvert.DeserializeObject( json, new DollarIdPreservingConverter()); 

Nota importante: es posible que tengas la tentación de decorar tus clases con un atributo JsonConverter lugar de pasar el convertidor a la llamada DeserializeObject , pero no hagas esto: hará que el convertidor entre en un bucle recursivo hasta que la stack se desborde. (Hay una manera de hacer que el convertidor funcione con el atributo, pero tendría que volver a escribir el método ReadJson para crear manualmente el objeto objective y llenar sus propiedades en lugar de llamar a jo.ToObject(objectType) . Es factible, pero una un poco más desordenado.)

Hazme saber si esto funciona para ti.

El problema es el signo $, por lo tanto, una solución alternativa sería:

Elimine $ de la anotación JsonProperty.

 [JsonProperty ("id")] public string Id { get; set; } 

En su código, reemplace el carácter especial $

 string response = "json as above"; FormDefinitionList root = JsonConvert.DeserializeObject (response.Replace("$id","id")); 

Editado como @BrianRogers sugiere

Esta respuesta resolvió el problema de $ id / $ ref para mí: Json.Net agregó $ id a los objetos EF a pesar de establecer PreserveReferencesHandling en “Ninguno”

En su implementación de DefaultContractResolver / IContractResolver, agregue esto;

 public override JsonContract ResolveContract(Type type) { var contract = base.ResolveContract(type); contract.IsReference = false; return contract; } 

EDITAR: Esto eliminará el $ id.