Obtenga valor de JToken que puede no existir (mejores prácticas)

117

¿Cuál es la mejor práctica para recuperar valores JSON que pueden no existir en C # usando Json.NET ?

En este momento estoy tratando con un proveedor JSON que devuelve JSON que a veces contiene ciertos pares clave / valor, y otras veces no. He estado usando (quizás incorrectamente) este método para obtener mis valores (ejemplo para obtener un doble):

if(null != jToken["width"])
    width = double.Parse(jToken["width"].ToString());
else
    width = 100;

Eso funciona bien, pero cuando hay muchos, resulta engorroso. Terminé escribiendo un método de extensión, y solo después de escribirlo me pregunté si tal vez estaba siendo estúpido ... de todos modos, aquí está el método de extensión (solo incluyo casos para doble y cadena, pero en realidad tengo bastantes más):

public static T GetValue<T>(this JToken jToken, string key,
                            T defaultValue = default(T))
{
    T returnValue = defaultValue;

    if (jToken[key] != null)
    {
        object data = null;
        string sData = jToken[key].ToString();

        Type type = typeof(T);

        if (type is double)
            data = double.Parse(sData);
        else if (type is string)
            data = sData;

        if (null == data && type.IsValueType)
            throw new ArgumentException("Cannot parse type \"" + 
                type.FullName + "\" from value \"" + sData + "\"");

        returnValue = (T)Convert.ChangeType(data, 
            type, CultureInfo.InvariantCulture);
    }

    return returnValue;
}

Y aquí hay un ejemplo del uso del método de extensión:

width = jToken.GetValue<double>("width", 100);

Por cierto, perdone lo que puede ser una pregunta realmente tonta, ya que parece que debería haber una función incorporada para ... Probé la documentación de Google y Json.NET , sin embargo, no soy capaz de encontrar la solución para mi pregunta o no está clara en la documentación.

Paul Hazen
fuente
Sé que es un poco tarde, pero es posible que desee probar esta versión simplificada de GetValueabajo
LB

Respuestas:

210

Para esto es más o menos el método genérico Value(). Obtiene exactamente el comportamiento que desea si lo combina con tipos de valor que aceptan valores NULL y el ??operador:

width = jToken.Value<double?>("width") ?? 100;
svick
fuente
4
Es un método de extensión.
Dave Van den Eynde
2
@PaulHazen, no es tan malo ... Sólo reinventó un poco la rueda, eso es todo.
devinbost
Esto no funciona si "ancho" no existe en el json y JToken es nulo
Deepak
2
@Deepak Funciona si "ancho" no existe. Por supuesto que no funciona si lo jTokenes null, pero eso no es lo que hizo la pregunta. Y usted puede fijar fácilmente que al utilizar el operador condicional nula: width = jToken?.Value<double?>("width") ?? 100;.
svick
1
JToken.Value<T>lanza una excepción si el JToken es un JValue
Kyle Delaney
22

Escribiría GetValuelo siguiente

public static T GetValue<T>(this JToken jToken, string key, T defaultValue = default(T))
{
    dynamic ret = jToken[key];
    if (ret == null) return defaultValue;
    if (ret is JObject) return JsonConvert.DeserializeObject<T>(ret.ToString());
    return (T)ret;
}

De esta manera puede obtener el valor no solo de los tipos básicos sino también de los objetos complejos. Aquí hay una muestra

public class ClassA
{
    public int I;
    public double D;
    public ClassB ClassB;
}
public class ClassB
{
    public int I;
    public string S;
}

var jt = JToken.Parse("{ I:1, D:3.5, ClassB:{I:2, S:'test'} }");

int i1 = jt.GetValue<int>("I");
double d1 = jt.GetValue<double>("D");
ClassB b = jt.GetValue<ClassB>("ClassB");
LB
fuente
Eso es bastante bueno, pero me gusta la separación de preocupaciones que me da solo obtener tipos de datos simples. Aunque la noción de esa separación es un poco borrosa cuando se trata de análisis JSON. Dado que implemento un modelo de observador / observable (con mvvm también), tiendo a mantener todo mi análisis en un solo lugar y lo mantengo simple (parte de eso es también la imprevisibilidad de los datos que me devuelven).
Paul Hazen
@PaulHazen No puedo decir que te entiendo. Tu pregunta fue retrieving JSON values that may not even existy todo lo que propuse fue cambiar tu GetValuemétodo. Creo que funciona como tú quieres
LB
Ojalá pueda ser un poco más claro esta vez. Su método funciona muy bien y lograría exactamente lo que quiero. Sin embargo, el contexto más amplio que no se explica en mi pregunta es que el código en particular en el que estoy trabajando es un código que quiero que sea altamente transferible. Si bien es discutible que su método se interponga en el camino, presenta la capacidad de deserializar objetos de GetValue <T>, que es un patrón que quiero evitar por el simple hecho de mover mi código a una plataforma que tiene un mejor analizador JSON (digamos , Win8 por ejemplo). Entonces, por lo que pregunté, sí, su código sería perfecto.
Paul Hazen
9

Así es como puede verificar si el token existe:

if (jobject["Result"].SelectToken("Items") != null) { ... }

Comprueba si existe "Elementos" en "Resultado".

Este es un ejemplo NO funcional que causa una excepción:

if (jobject["Result"]["Items"] != null) { ... }
Artur Alexeev
fuente
3

Simplemente puede encasillar, y hará la conversión por usted, por ejemplo

var with = (double?) jToken[key] ?? 100;

Volverá automáticamente nullsi dicha clave no está presente en el objeto, por lo que no hay necesidad de probarla.

Dave Van den Eynde
fuente
1

TYPE variable = jsonbody["key"]?.Value<TYPE>() ?? DEFAULT_VALUE;

p.ej

bool attachMap = jsonbody["map"]?.Value<bool>() ?? false;

Downhillski
fuente
1

Esto se encarga de los nulos

var body = JObject.Parse("anyjsonString");

body?.SelectToken("path-string-prop")?.ToString();

body?.SelectToken("path-double-prop")?.ToObject<double>();
Max
fuente