Uzyskaj wartość z JToken, która może nie istnieć (najlepsze praktyki)

117

Jaka jest najlepsza praktyka pobierania wartości JSON, które mogą nawet nie istnieć w języku C # przy użyciu Json.NET ?

W tej chwili mam do czynienia z dostawcą JSON, który zwraca JSON, który czasami zawiera pewne pary klucz / wartość, a czasami nie. Używałem (być może niepoprawnie) tej metody, aby uzyskać moje wartości (przykład dla uzyskania podwójnego):

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

Teraz to działa dobrze, ale gdy jest ich dużo, jest to kłopotliwe. Skończyło się na napisaniu metody rozszerzającej i dopiero po jej napisaniu zastanawiałem się, czy może nie byłem głupi ... tak czy inaczej, oto metoda rozszerzająca (uwzględniam tylko przypadki dla double i string, ale w rzeczywistości mam sporo więcej):

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;
}

A oto przykład użycia metody rozszerzenia:

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

Swoją drogą , proszę wybaczyć to, co może być naprawdę głupie pytanie, bo wygląda na to, że coś powinno mieć wbudowaną funkcję dla ... Wypróbowałem Google i dokumentację Json.NET , ale nie jestem w stanie znaleźć rozwiązania moje pytanie lub nie jest to jasne w dokumentacji.

Paul Hazen
źródło
Wiem, że jest trochę za późno, ale możesz spróbować tej uproszczonej wersji GetValueponiżej
LB

Odpowiedzi:

210

Do tego właśnie służy metoda ogólna Value(). Otrzymasz dokładnie takie zachowanie, jakie chcesz, jeśli połączysz je z typami wartości dopuszczalnej wartości null i ??operatorem:

width = jToken.Value<double?>("width") ?? 100;
svick
źródło
4
To metoda przedłużająca.
Dave Van den Eynde
2
@PaulHazen, nie jest tak źle ... Tylko trochę wymyśliłeś koło na nowo.
devinbost
To nie działa, jeśli „width” nie istnieje w pliku JSON, a JToken jest pusty
Deepak
2
@Deepak Działa, jeśli „szerokość” nie istnieje. Oczywiście to nie działa, jeśli jTokentak null, ale nie o to zadawano pytanie. I można łatwo naprawić za pomocą zerowej operator warunkowy: width = jToken?.Value<double?>("width") ?? 100;.
Svick
1
JToken.Value<T>zgłasza wyjątek, jeśli JToken jest wartością JValue
Kyle Delaney
22

Napisałbym GetValuejak poniżej

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;
}

W ten sposób można uzyskać wartość nie tylko podstawowych typów, ale także złożonych obiektów. Oto próbka

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");
FUNT
źródło
To całkiem fajne, ale podoba mi się oddzielenie problemów, które daje mi tylko uzyskanie prostych typów danych. Chociaż pojęcie tego oddzielenia jest nieco rozmyte, jeśli chodzi o analizę JSON. Ponieważ implementuję model obserwatora / obserwowalnego (również z mvvm), staram się trzymać całą analizę w jednym miejscu i utrzymywać ją w prostocie (po części jest to również nieprzewidywalność zwracanych do mnie danych).
Paul Hazen,
@PaulHazen Nie mogę powiedzieć, że cię rozumiem. Twoje pytanie brzmiało retrieving JSON values that may not even existi zaproponowałem tylko zmianę GetValuemetody. Myślę, że działa tak, jak chcesz
LB
Mam nadzieję, że tym razem mogę być trochę bardziej przejrzysty. Twoja metoda działa świetnie i pozwoli osiągnąć dokładnie to, czego chcę. Jednak szerszy kontekst, który nie został wyjaśniony w moim pytaniu, jest taki, że konkretny kod, nad którym pracuję, jest kodem, który chcę, aby był wysoce transferowalny. Chociaż można się spierać, że twoja metoda przeszkadza, wprowadza ona możliwość deserializacji obiektów z GetValue <T>, co jest wzorcem, którego chcę uniknąć, aby przenieść mój kod na platformę, która ma lepszy parser JSON (powiedzmy , Na przykład Win8). Tak więc, o co prosiłem, tak, twój kod byłby doskonały.
Paul Hazen,
9

Oto jak możesz sprawdzić, czy token istnieje:

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

Sprawdza, czy „Elementy” istnieją w „Wynikach”.

To jest NIE działający przykład, który powoduje wyjątek:

if (jobject["Result"]["Items"] != null) { ... }
Artur Alexeev
źródło
3

Możesz po prostu typecast, a on dokona konwersji za Ciebie, np

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

Automatycznie zwróci, nulljeśli ten klucz nie jest obecny w obiekcie, więc nie ma potrzeby testowania go.

Dave Van den Eynde
źródło
1

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

na przykład

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

Downhillski
źródło
1

To zajmuje się zerami

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

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

body?.SelectToken("path-double-prop")?.ToObject<double>();
Maks
źródło