Laden...

Parsen von JSON-formatierten RKI-Daten

Erstellt von OlafSt vor 2 Jahren Letzter Beitrag vor 2 Jahren 1.111 Views
O
OlafSt Themenstarter:in
79 Beiträge seit 2011
vor 2 Jahren
Parsen von JSON-formatierten RKI-Daten

Hallo Freunde,

ich bin wieder mal zu doof. Ich versuche, die REST-Daten des RKI mal selbst abzufragen und auszuwerten. Doch bereits beim Parsen des JSON scheitert das ganze. Vielleicht kann mich jemand in die passende Richtung schubsen ?

Folgende URL wird abgefragt: https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json

Das ganze mache ich mit folgenden paar Zeilen:


        async static void Search()
        {
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Add("ContentType", "application/json");

            string uri = "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json";
            HttpResponseMessage responseMessage = await client.GetAsync(uri);
            string contentString = await responseMessage.Content.ReadAsStringAsync();
            dynamic jsonText = JsonConvert.DeserializeObject(contentString); //Das hier funktioniert
            var data = JsonConvert.DeserializeObject<attributes>(contentString); //Hier kommt nix

            Console.WriteLine(jsonText);
        }

Ich habe herausgefunden, das man die passenden Klassen selbst erst bauen muss (ich dachte, das ginge auch irgendwie "von selbst"), was ich in dieser Form gemacht habe:


    class attributes
    {
        public int IdBundesland { get; set; }
        public string Bundesland { get; set; }
        public string Landkreis { get; set; }
        public string Altersgruppe { get; set; }
        public string Geschlecht { get; set; }
        public int AnzahlFall { get; set; }
        public int AnzahlTodesfall { get; set; }
        public int ObjectId { get; set; }
        public DateTime Meldedatum { get; set; }
        public string IdLandkreis { get; set; }
        public string Datenstand { get; set; }
        public int NeuerFall { get; set; }
        public int NeuerTodesfall { get; set; }
        public DateTime Refadtum { get; set; }
        public int NeuGenesen { get; set; }
        public int AnzahlGenesen { get; set; }
        public int IstErkrankungsbeginn { get; set; }
        public string Altersgruppe2 { get; set; }
    }

    class features
    {
        public int ObjectId { get; set; }
        public attributes[] attributes { get; set; }
    }

wobei ich auf die features-Klasse erst kam, als mir klar wurde, das das ein Array aus attributes ist... Oder sowas. Ich habe da überhaupt keinen Durchblick und bisherige Googelei ergibt zwar tonneweise Code, wie man es macht - aber nur mit händischem JSON-String und einer Klasse mit zwei Membern... Also nicht sehr informativ.

Was mache ich da falsch ?

C
55 Beiträge seit 2020
vor 2 Jahren

Hallo,

Json parsen ist eigentlich kein Hexenwerk, außer man heißt Rockstar. Ich würde das Json erstmal vernüftig formattieren, damit ich besser sehen, was für Keys vorhanden sind, dazu eine passende Modelklassen erstellen/erzeugen.

Grüße

16.834 Beiträge seit 2008
vor 2 Jahren

Ja, da ist viel im Argen und Du machst Dir das Leben viel schwerer, als man es sich machen muss.

Ich habe da überhaupt keinen Durchblick und bisherige Googelei ergibt zwar tonneweise Code, wie man es macht - aber nur mit händischem JSON-String und einer Klasse mit zwei Membern... Also nicht sehr informativ.

Wichtig wäre, dass Du Dir ein paar Basics aneignest, und per Google lässt sich auch super einfach Serialisieren und Deserialisieren von JSON mit C# – .NET finden, das alles beinhaltet, was Du hier brauchst, aber womöglich bei Deiner Recherche übersehen hast.
Kann auch nur schwer glauben, dass bei einem der wichtigsten Themen in der Programmierung überhaupt (Json) so wenig findest 🙂

ich dachte, das ginge auch irgendwie "von selbst"

Natürlich ist dem nicht so, denn eine Runtime kann nicht hellsehen. Aber es ist schon ein grober Fehler, dass die URL Dir ein anderes Json liefert, als dass Deine Klassen darstellen.
Lass ich mit Visual Studio - einfach Edit -> Paste Special -> Json to Class) (es gibt aber auch tausende an Treffer in Google, die das alles Online im Browser erzeugen können) das Json als Klassen generieren, dann sieht das so aus:


public class Rootobject
{
    public string objectIdFieldName { get; set; }
    public Uniqueidfield uniqueIdField { get; set; }
    public string globalIdFieldName { get; set; }
    public Field[] fields { get; set; }
    public bool exceededTransferLimit { get; set; }
    public Feature[] features { get; set; }
}

public class Uniqueidfield
{
    public string name { get; set; }
    public bool isSystemMaintained { get; set; }
}

public class Field
{
    public string name { get; set; }
    public string type { get; set; }
    public string alias { get; set; }
    public string sqlType { get; set; }
    public object domain { get; set; }
    public object defaultValue { get; set; }
    public int length { get; set; }
}

public class Feature
{
    public Attributes attributes { get; set; }
}

public class Attributes
{
    public int IdBundesland { get; set; }
    public string Bundesland { get; set; }
    public string Landkreis { get; set; }
    public string Altersgruppe { get; set; }
    public string Geschlecht { get; set; }
    public int AnzahlFall { get; set; }
    public int AnzahlTodesfall { get; set; }
    public int ObjectId { get; set; }
    public long Meldedatum { get; set; }
    public string IdLandkreis { get; set; }
    public string Datenstand { get; set; }
    public int NeuerFall { get; set; }
    public int NeuerTodesfall { get; set; }
    public long Refdatum { get; set; }
    public int NeuGenesen { get; set; }
    public int AnzahlGenesen { get; set; }
    public int IstErkrankungsbeginn { get; set; }
    public string Altersgruppe2 { get; set; }
}

Wichtig ist, dass Klassen zum Serialisieren immer public sein müssen, was bei Dir auch fehlt. Und auch solltest Du nach so einem Generieren immer die Typen validieren; zB. sieht man hier ein paar object-Eigenschaften, die vermutlich falsch sind.
Datums-Werte - sofern nicht anders beschrieben - sollten aber immer mit DateTimeOffset und nicht mit DateTime serialisiert und verarbeitet werden, siehe [FAQ] DateTime vs. DateTimeOffset und der Umgang mit Zeiten in .NET

Man sieht aber besonders beim Datumswert, dass derjenige, der die RKI API gebaut hat, absolut keine Ahnung von standardisierter Datumübertragung hat, denn einige Datumswerte werden als Strings und nicht nach ISO 8601, das man verwenden sollte, deklariert; andere als long (vermutlich Unix Timestamp)
Das bedeutet, dass Du einen DateTimeOffset-Parser einbauen musst, um die Datumswerte korrekt zu serialisieren - oder Du empfänst eben strings/longs und beachtest das in Deiner Verarbeitung.
Aber dass die deutsche Software rund um Behörden in 99% einfach unstandardisierter Müll ist: bezeichnend - und leider nicht ungewöhnlich.

Deine Klassen aber werden so nicht funktionieren, weil sie überhaupt nicht dem Json entsprechen.
Dass Du noch das (aus .NET 5 sicht) veraltete JsonConvert reagiert dann eben so, dass null raus kommt. Das modernere System.Text.Json, das man verwenden sollte, wirft ein Fehler.

Der Code für das Abfragen kann daher einfach folgendermaßen sein:


string uri = "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json";
string jsonString = await client.GetStringAsync(uri);

var json = JsonSerializer.Deserialize<Rootobject>(jsonString);

In der HttpClient Dokumentation siehst Du ebenfalls ein Beispiel, bei dem Du gar nicht mehr manuell serialisieren musst.


var json = await client.GetFromJsonAsync<Rootobject>(uri);

Ein weiterer struktureller Fehler ist übrigens, dass Du den HttpClient ständig neu erzeugst, siehe HttpClient Klasse (System.Net.Http)

HttpClient soll einmal instanziiert und während der gesamten Lebensdauer einer Anwendung wieder verwendet werden. Durch das Instanziieren einer HttpClient-Klasse für jede Anforderung wird die Anzahl der verfügbaren Sockets bei starker Auslastung erschöpft. Dies führt zu SocketException-Fehlern.

Daher Fazit:

Was mache ich da falsch ?

alles 1:1 sehr simpel in den Docs beschrieben
Einfach bisschen lesen - dazu sind sie da 😉

Json parsen ist eigentlich kein Hexenwerk, außer man heißt Rockstar.

.. uh, das ist ein Insider, den die meisten wohl nicht verstehen.
Hab auch googlen müssen, was Du damit meinen könntest 😉

O
OlafSt Themenstarter:in
79 Beiträge seit 2011
vor 2 Jahren

Wow, das nenne ich mal einen wirklich lehrreichen Schubser 👍 👍

Zunächst muss ich zu meiner Verteidigung sagen, das ich all diese Tutorials - auch das von @Abt vorgeschlagene von Microsoft - sehr wohl gelesen habe. Ich mache das mit dem Programmieren ja nun auch schon den einen oder anderen Tag, habe das alles verstanden und war daher tatsächlich so übermütig zu glauben, das ich das Zeug vom RKI mal eben schnell zusammenhäkeln könnte. Klassen in JSON zu verwandeln und wieder zurück ist ja nun auch kein Hexenwerk (das mit Rockstar war mir auch nicht bekannt 🙂 ), wenn man die Tutorials durch hat.

Allen diesen Tutorials, auch denen bei Youtube und wo auch immer - die fast ausnahmslos Newtonsoft verwenden, daher dachte ich, nimmste mal das - ist allerdings eines gemein: Sie fertigen eine Klasse mit mehr oder weniger Komplexitätsgrad - meist mit weniger bis keinem. Dann zeigen sie dir, wie man daraus JSON macht und dann, wie man das wieder in Klassen verwandelt. So weit, so gut, so einfach.

Kein einziges, das ich fand, befasst sich mit dem Thema: Hier ist JSON, mach mal. Genau das habe ich aber versucht. Das JSON in den Simpel-Tuts ist natürlich auch entsprechend simpel, konnte also auch nicht wirklich als Vergleich dienen: "Aha, so sieht das in JSON aus, das machen die im RKI auch so, also muss meine Klasse so und so aussehen". Wenn ich sehe, was @Abt da für einen Haufen Klassen generiert hat, frage ich mich: Welchen Schuss hab ich nicht gehört, das ich anhand des MSDN-Tutorials nicht auf Anhieb so eine Klassenstruktur erzeugen konnte ? 😉

Übrigens habe ich gestern abend nach Eingabe von "C# JSON in Klassen umwandeln" bei Google den Trick mit Edit -> Paste Special gesehen. Ich habe ernsthaft nach diesem Menüpunkt oder etwas vergleichbarem gesucht, aber erst heute morgen mit einem frischen Hirn entdeckt, das es da "Inhalte einfügen -> JSON als Klassen" gibt. My bad, das ich so blind war und nicht drauf komme, das "Paste special" nach "Inhalte einfügen" übersetzt wurde. Hätte ich das früher gesehen, hätte ich euch gar nicht behelligt, dann wären die Klassen ja schon fertig gewesen und das Zeug hätte womöglich dann schon funktioniert 🙂 🙂

Auf jeden Fall habe ich viel gelernt durch den kurzen Abschnitt, den @Abt dort geschrieben hat. Newtonsoft fliegt raus, Bordmittel sind eigentlich immer die bessere Wahl (als alter Delphi-Coder lernt man das schnell). Ich werde das ganze mit dem System.Text.JSon umsetzen - eine Fehlermeldung ist ohnehin am Anfang besser als ein Null. Und ich habe gelernt, das man gar nicht JSON-Profi sein muss, um aus JSON nun Klassen zu machen, das vereinfacht das ganze. Ach ja: Der HttpClient wird nur einmal erzeugt, das ist ne Kommandozeilen-Anwendung, die nur einmal durchläuft. Wird das ganze mal komplexer und mit WPF-GUI, dann komt da natürlich n using drumherum bzw. die Instanz wird nur einmal erzeugt und bis zum Programmende beibehalten.

Nochmals vielen Dank !

[Edit]
Nachdem ich die Klassen selbst generiert habe und diese Klassenstruktur da sehe, stimme ich @Abt zu. Das war wohl ein Werkstudent, der das erzeugt hat.
[/Edit]

16.834 Beiträge seit 2008
vor 2 Jahren

die fast ausnahmslos Newtonsoft verwenden

Newtonsoft - entwickelt von James Newton-King - ist das am meisten verbreitete NuGet Paket im .NET Ökosystem und war vor System.Text.Json der einzige wirkliche Weg in .NET mit Json umgehen. Die Framework-Implementierung war murks. James ist nen super netter Typ, Coffeebean und ich haben ihn einige Male persönlich treffen können.
James ist mitterweile bei Microsoft und primär im ASP.NET Core Ökosystem unterwegs (aktuell vorallem rund um gRPC und HTTP/3).

Und mittlerweile gibt es eben auch System.Text.Json, das man für neue Entwicklungen nutzen sollte, das aber eben noch relativ jung ist.
Daher findest Du im Netz sehr viel zu Newtonsoft, aber in den Microsoft Docs findest Du fast nur noch System.Text.Json.

Json gehört mittlerweile einfach als Basis-Werkzeug an, und zwar nicht nur für die Web-Welt, sondern für die gesamte Software Welt.
Es sind einfach Basics, die ein inhaltliches Doc wie MSDoc nicht behandelt. Und Basics findest eigentlich einfach via Google Suche nach "what is json"

Und wenn ich nach Google Suche nach "what is json c#" suche finde ich sehr schnell https://www.c-sharpcorner.com/article/from-zero-to-hero-in-json-with-c-shar/, was sehr gut aussieht, die Herkunft von Json zeigt und genau wie Du es gern hättest: vom Zero to Hero.
Daher begründe ich Dein Ergebnis einfach mal mit Deinen entsprechenden Such-Mustern 😉

das ist ne Kommandozeilen-Anwendung, die nur einmal durchläuft.

Dann will ich dich damit auf den nächsten Fehler hinweisen: async void ist ein Pitfall.
Async/Await – Bewährte Verfahren bei der asynchronen Programmierung

Bordmittel sind eigentlich immer die bessere Wahl

Dem ist garantiert nicht so.

Das .NET Ökosystem lebt von Open Source, so wie andere Ökosysteme wie Java, NodeJS und Co auch.
Und Open Source Entwickler sind teilweise extrem gute Entwickler, die durchaus auch besseres Zeug produzieren können als der Hersteller.
Genau so ist Newtonsoft entstanden (siehe oben).

Prinzipiell ist es okay auf Bordmittel nutzen; aber nicht blind.
Das Forum hier hat auch einigen Code, dessen Funktionen durchaus von Microsoft Implementierungen genutzt werden können, aber diese Fehler haben / schlecht umgesetzt sind und wir daher das Zeug selbst geschrieben haben.
Manchmal sind da auch die Microsofties resistent, wenn man ihnen Verbesserungs-Feedback gibt (Beispiel => trotz nachgewiesener Race Condition hat Microsoft nicht so lust den Fehler zu korrigieren und begründet das irgendwie haarsträubend: Fixed race conditions in FeatureManagerSnapshot)