2012-08-13 56 views
47

TL; DR VersiónConversión de un JToken (o cadena) a un determinado tipo

Tengo un objeto de tipo JToken (pero también puede ser un string) y tengo que convertirlo en un tipo de contenido en la variable type:

Type type = typeof(DateTime); /* can be any other Type like string, ulong etc */ 
var obj = jsonObject["date_joined"]; /* contains 2012-08-13T06:01:23Z+05:00 */ 
var result = Some_Way_To_Convert(type, obj); 

lo anterior result debe ser un objeto DateTime con el valor dado en date_joined.

historia completa

estoy usando tanto RestSharp y Json.NET en un proyecto de Windows Phone y estoy atascado al intentar deserializar respuestas JSON de una API REST.

Lo que estoy tratando de lograr es escribir un método genérico que pueda mapear fácilmente mi respuesta JSON en mis entidades CLR, al igual que ya puede hacer con RestSharp. El único problema es que la implementación predeterminada de RestSharp no funciona para mí y no puede analizar correctamente el JSON ya que la respuesta no siempre devuelve todas las propiedades (no devuelvo campos que son null del servidor REST).

Es por eso que decidí usar el Json.NET de Newtonsoft ya que tiene un motor deserializador Json mucho más potente. Desafortunadamente, no admite nombres de propiedades/campos difusos como RestSharp (o no he encontrado ninguno), por lo que tampoco se correlaciona correctamente con mis entidades CLR cuando uso algo como decir JsonConvert.DeserializeObject<User>(response.Content).

Aquí es lo que mi JSON se parece a (un ejemplo en realidad):

{ 
    "id" : 77239923, 
    "username" : "UzEE", 
    "email" : "[email protected]", 
    "name" : "Uzair Sajid", 
    "twitter_screen_name" : "UzEE", 
    "join_date" : "2012-08-13T05:30:23Z05+00", 
    "timezone" : 5.5, 
    "access_token" : { 
     "token" : "nkjanIUI8983nkSj)*#)([email protected]", 
     "scope" : [ "read", "write", "bake pies" ], 
     "expires" : 57723 
    }, 
    "friends" : [{ 
     "id" : 2347484", 
     "name" : "Bruce Wayne" 
    }, 
    { 
     "id" : 996236, 
     "name" : "Clark Kent" 
    }] 
} 

Y es un ejemplo de mis entidades CLR aquí:

class AccessToken 
{ 
    public string Token { get; set; } 
    public int Expires { get; set; } 
    public string[] Scope { get; set; } 
    public string Secret { get; set; } /* may not always be returned */ 
} 

class User 
{ 
    public ulong Id { get; set; } 
    public string UserName { get; set; } 
    public string Email { get; set; } 
    public string Name { get; set; } 
    public string TwitterScreenName { get; set; } 
    public DateTime JoinDate { get; set; } 
    public float Timezone { get; set; } 
    public bool IsOnline { get; set; } /* another field that might be blank e.g. */ 

    public AccessToken AccessToken { get; set; } 

    public List<User> Friends { get; set; } 
} 

Lo que quiero es una forma sencilla de analizar los anteriores JSON en los objetos CLR dados. He buscado en el código fuente de RestSharp y he visto el código JsonDeserializer y he podido escribir un método de extensión genérico DeserializeResponse<T> en JObject que debería devolver un objeto del tipo T. El uso previsto es algo como esto:

var user = JObject.Parse(response.Content).DeserializeResponse<User>(); 

El método anterior se debe analizar la respuesta JSON dado a un objeto de entidad de usuario. He aquí un fragmento de código real de lo que estoy haciendo en el método DeserializeResponse<User> extensión (su basado en el código RestSharp):

public static T DeserializeResponse<T>(this JObject obj) where T : new() 
{ 
    T result = new T(); 
    var props = typeof(T).GetProperties().Where(p => p.CanWrite).ToList(); 
    var objectDictionary = obj as IDictionary<string, JToken>; 

    foreach (var prop in props) 
    { 
     var name = prop.Name.GetNameVariants(CultureInfo.CurrentCulture).FirstOrDefault(n => objectDictionary.ContainsKey(n)); 
     var value = name != null ? obj[name] : null; 

     if (value == null) continue; 

     var type = prop.PropertyType; 

     if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 
     { 
      type = type.GetGenericArguments()[0]; 
     } 

     // This is a problem. I need a way to convert JToken value into an object of Type type 
     prop.SetValue(result, ConvertValue(type, value), null); 
    } 

    return result; 
} 

supongo que la conversión debe ser una cosa muy fácil de hacer ya que es un trivial tarea. Pero he estado buscando desde hace bastante tiempo y todavía no he encontrado una manera de hacerlo a través de Json.NET (y seamos honestos, la documentación es un tanto comprensible y le faltan algunos ejemplos).

Cualquier ayuda sería realmente apreciada.

+0

Cualquier ayuda aquí? Esta es la segunda vez que hago una pregunta sobre SO y, sin embargo, nunca recibí una respuesta:/ –

+0

http://stackoverflow.com/questions/9589218/get-value-from-jtoken-that-may-not-exist- mejores prácticas – Guillaume

+0

@Guillaume Gracias. Ya lo había leído antes de publicarlo. El problema es que estoy escribiendo un analizador que debería ser capaz de manejar cualquier valor que encuentre, por lo que un simple 'jToken.value ()' no funcionará, ya que no sé que sería un 'doble'. No sé el tipo de antemano. –

Respuesta

90

Ahora hay un método ToObject.

var obj = jsonObject["date_joined"]; 
var result = obj.ToObject<DateTime>(); 

También funciona con cualquier tipo complejo, y obedecen a reglas JsonPropertyAttribute

var result = obj.ToObject<MyClass>(); 

public class MyClass 
{ 
    [JsonProperty("date_field")] 
    public DateTime MyDate {get;set;} 
} 
+2

+1 Esta es una forma mucho más limpia de hacerlo que llamar '.ToString()' y luego deserializarlo. No estoy 100% seguro de cuál es la diferencia debajo del capó, pero esto es mucho mejor sintácticamente. – theyetiman

+2

Si no conoce el tipo como tiempo de diseño, hay una versión sobrecargada de ToObject() que toma Tipo como parámetro. Estoy de acuerdo. Esta es la solución más limpia. Realmente debería ser la "respuesta". –

22
System.Convert.ChangeType(jtoken.ToString(), targetType); 

o

JsonConvert.DeserializeObject(jtoken.ToString(), targetType); 

--EDIT--

Uzair, aquí es un ejemplo completo sólo para mostrar que trabajan

string json = @"{ 
     ""id"" : 77239923, 
     ""username"" : ""UzEE"", 
     ""email"" : ""[email protected]"", 
     ""name"" : ""Uzair Sajid"", 
     ""twitter_screen_name"" : ""UzEE"", 
     ""join_date"" : ""2012-08-13T05:30:23Z05+00"", 
     ""timezone"" : 5.5, 
     ""access_token"" : { 
      ""token"" : ""nkjanIUI8983nkSj)*#)([email protected]"", 
      ""scope"" : [ ""read"", ""write"", ""bake pies"" ], 
      ""expires"" : 57723 
     }, 
     ""friends"" : [{ 
      ""id"" : 2347484, 
      ""name"" : ""Bruce Wayne"" 
     }, 
     { 
      ""id"" : 996236, 
      ""name"" : ""Clark Kent"" 
     }] 
    }"; 

var obj = (JObject)JsonConvert.DeserializeObject(json); 
Type type = typeof(int); 
var i1 = System.Convert.ChangeType(obj["id"].ToString(), type); 
var i2 = JsonConvert.DeserializeObject(obj["id"].ToString(), type); 
+3

Ambos no funcionan. 'System.Convert.ChangeType()' necesita un tercer parámetro 'IFormatProvider'. Y 'JsonConvert.DeserializeObject()' arroja una excepción: "Carácter inesperado encontrado al analizar el valor: U. Ruta '', línea 0, posición 0." –

+0

@UzairSajid Probé esos códigos antes de publicarlos. 'IFormatProvider' es opcional. Para la segunda parte. Si publica su código + datos, puedo intentar trabajar en él. –

+2

No funcionó exactamente como dijiste (en realidad, la firma del método para 'System.Convert.ChangeType()' es diferente en Windows Phone) porque necesita un tercer parámetro obligatorio 'IFormatProvider'. Pero pude hacerlo funcionar pasando un 'null' como tercer parámetro y manejando unos pocos casos de borde explícitamente (como analizar un valor de' "1" 'a' bool true' y 'string' a' URI') por ahora. Veremos si todavía funciona a largo plazo. –

1
var i2 = JsonConvert.DeserializeObject(obj["id"].ToString(), type); 

tiros una excepción de análisis d debido a las comillas faltantes en torno al primer argumento (creo). Lo tengo que trabajar agregando las comillas:

var i2 = JsonConvert.DeserializeObject("\"" + obj["id"].ToString() + "\"", type); 
0

que era capaz de convertir mediante el siguiente método para mi WebAPI:

[HttpPost] 
public HttpResponseMessage Post(dynamic item) // Passing parameter as dynamic 
{ 
JArray itemArray = item["Region"]; // You need to add JSON.NET library 
JObject obj = itemArray[0] as JObject; // Converting from JArray to JObject 
Region objRegion = obj.ToObject<Region>(); // Converting to Region object 
} 
Cuestiones relacionadas