# JSON-Objekte richtig parsen



## Tueftler (9. Jan 2020)

Hallo,
zum Auslesen von json-Daten verwende ich json simple. Beim Parser  steh ich vor der Schwierigkeit, dass die key/value Beispiele im Internet immer nur Keys mit eindeutigem Namen haben. Ich will jedoch einen jason-String parsen, der verschachtelt ist, wobei die keys in verschiedenen Blöcken denselben Namen haben können. Wie mach ich das?
Beispiel für einen verschachtelten String (values muss man sich dazudenken);

```
[
  {
    "lang":
    "hits": [
      {
        "type":
        "opendict":
        "roms": [
          {
            "headword":
            "headword_full":
            "wordclass":
            "arabs": [
              {
                "header":
                "translations": [
                  {
                    "source":
                    "target": "wollen wir (ein)mal sehen"
                  },
                  {
                    "source":
                    "target":
                  },
                ]
              },
              {
                "header":
                "translations": [
                  {
                    "source":
                    "target":
                  },
                  {
                    "source":
                    "target":
                  },
                ]
              },
            ]
          }
        ]
      }
    ]
  }
]
```


----------



## White_Fox (9. Jan 2020)

JSON-Parser gibt es genug, die Mühe haben sich andere bereits gemacht. Warum nimmst du nicht so einen?


----------



## MoxxiManagarm (9. Jan 2020)

Tueftler hat gesagt.:


> Ich will jedoch einen jason-String parsen, der verschachtelt ist, wobei die keys in verschiedenen Blöcken denselben Namen haben können



Ein "normales" key-value Paar ist ein JSONObject. Was du meinst sind vermutlich die JSONArrays in deinem json-String. Du musst Objekte und Arrays auch als solche behandeln.


----------



## Tueftler (9. Jan 2020)

White_Fox:
Richtig! Aber wenn es sich vermeiden lässt, will ich auch keinen Parser schreiben, sondern nur* richtig anwenden*! Beispiel in meinem Listing: Wenn ich das zweite Target in der zweiten Translation auslesen will, kann ich *nicht* schreiben:

JSONObject jsonObject = (JSONObject) parser.parse(jsonString);
String target = (String) jsonObject.get("target");

denn woher soll der get wissen, welches target von den vieren im Listing gemeint ist?

MoxxiManagarm:
"Ein "normales" key-value Paar ist ein JSONObject" OK, aber ein JSONObject kann viele key-value Paare und zusätzlich noch ein JASONArray enthalten, oder sehe ich das falsch?
Wenn ich oben geschrieben habe "Die values muss man sich dazudenken", dann war damit gemeint, die keys (Am Ende mit Doppelpunkt) sind geschrieben, die values hab ich weggelassen. Mit meinen (zugegeben rudimentären ) JSON-Kenntnissen kann ich in meinem Listing kein Array entdecken. Gibt es trotzdem da eins?
In den diversen JSON-Beschreibungen werden die key-value-Paare eines JSONObjects als MAP-ähnliche Sammlung bezeichnet, doch bei der MAP sind die keys eine SET-Collection, es gibt keine doppelten Einträge, bei meinem Listing oben, das aus dem realen Leben stammt, aber wohl. Das kann nur funktionieren, wenn der Parser die eckigen und geschweiften Klammern zu interpretieren weiß. Dann aber muss es auch Zugriffsmethoden auf die unterschiedlichen Blöcke geben und genau die such ich. Eine andere Möglichkeit: json simple heißt so, weil man nur mit einfachen json-Strukturen arbeiten kann, dann müsste ich eine andere lib nehmen. Aber das weiß ich nicht.


----------



## MoxxiManagarm (9. Jan 2020)

Tueftler hat gesagt.:


> oder sehe ich das falsch?


Jain, du hast recht ich habe mich etwas falsch ausgedrückt. Ein JSONObject ist eine Menge von keys und values. Die meisten values, die du als key-value-Paar bezeichnest sind aber Datentypen wie boolean und String. Ein Value kann aber auch ein anderen JSONObject oder ein JSONArray sein.



Tueftler hat gesagt.:


> Mit meinen (zugegeben rudimentären ) JSON-Kenntnissen kann ich in meinem Listing kein Array entdecken. Gibt es trotzdem da eins?


Du hast ganz viele Arrays ^^ Jedes []-Paar signalisiert eins. Dein gesamtes JSON ist ein Array, hat aber nur ein JSONObject mit den keys lang und hits. lang hat den value vermutlich vom typ string. hit hingegen hat einen JSONArray value. Auch dieses Array hat in deinem JSON nur einen Eintrag mit den keys type, opendict und roms. type und opendict sind vermutlich wieder strings, aber roms hat wieder ein JSONArray value. Auch dieses enthält nur ein JSONObject. Entgegen den übergeordneten Arrays hat hier das enthaltene JSONArray mit dem key arabs 2 enthaltene JSONObject. Weiter tiefer findest du noch das JSONArray translations, welches jeweils auch 2 JSONObject enthält.

PS.: Ich persönlich finde json simple ist für solche komplexen Strukturen nicht gemacht. json simple enthält leicht viel code mit Object cast.


----------



## White_Fox (9. Jan 2020)

Tueftler hat gesagt.:


> Aber wenn es sich vermeiden lässt, will ich auch keinen Parser schreiben, sondern nur* richtig anwenden*!


Ich hab unlängst mit Gson rumhantiert und fand das eigentlich ganz gut. Hier ist der Test:


```
public class JSONBTest {
    
    class Testclass{
        int someMember;
        int newMember;
        String s1;
        String anotherString;
    }
    
    public static void main(String[] args) {
        
//        Testclass tc = new Testclass();
//        tc.s1 = "Blah!";
//        tc.someMember = 23;
//       
//        Gson gson = new Gson();
//        System.out.println(gson.toJson(tc));
    
        // result from code above
        String deserializedString = "{\"someMember\":23,\"s1\":\"Blah!\"}";
        Type t = new TypeToken<Testclass>(){}.getType();
        Gson gson = new Gson();
        Testclass tc = gson.fromJson(deserializedString, t);
        
        System.out.println("someMember: " + tc.someMember);
        System.out.println("newMember: " + tc.newMember);
        System.out.println("s1: " + tc.s1);
        System.out.println("anotherString: " + tc.anotherString);
    }
}
```

Das ist ein etwas liebloser Test (und heißt JSONB weil ich ursprünglich das versuchen wollte, habs dann aber doch mit GSON gemacht). Der auskommentierte Code hat den "deserializedString" erstellt, ich habe erst den auskommentierten Teil laufen lassen und die Ausgabe rauskopiert.
Ich habe die Klasse nach dem ersten Test bewußt verändert (zwei Membervariablen habe ich hinzugefügt) weil ich mal sehen wollte wie GSON sich anstellt wenn ich Objekt speicher, die Klasse dann erweitere, und danach aus dem String wieder meine Klasse heraushole.


----------



## mihe7 (10. Jan 2020)

Nehmen wir mal 

```
{
  "search": [
    { "url": "https://startpage.com" },
    { "url": "https://duckduckgo.com" }
  ],
  "programming": [
    { "url": "https://stackoverflow.com" },
    { "url": "https://www.java-forum.org" }
  ]
}
```
an. Dann kann man sich das mal mit den jeweiligen Typen darstellen, wobei "a => b" bedeutet, dass a auf b abgebildet wird:

```
JsonObject{
  "search" => JsonArray[
    JsonObject{ "url" => "https://startpage.com" },
    JsonObject{ "url" => "https://duckduckgo.com" }
  ],
  "programming" => JsonArray[
    JsonObject{ "url" => "https://stackoverflow.com" },
    JsonObject{ "url" => "https://www.java-forum.org" }
  ]
}
```
Du hast ein JsonObject, das die beiden Schlüssel "search" und "programming" auf je ein JsonArray abbildet. In den Arrays befinden sich dann beliebig viele Einträge, die hier alle aus je einem JsonObject bestehen. Diese JsonObjects bilden nun jeweils einen Schlüssel "url" auf einen String ab. Das ist kein Problem, weil es sich ja um verschiedene Objekte handelt.

Und genauso arbeitest Du damit, z. B. mit JSON-P aka JSR 374. Skizze:

```
public List<String> getAllUrls(JsonObject bookmarks, String ... types) {
    List<String> result = new ArrayList<>();

    for (String type : types) {
        JsonArray bookmarkArray = bookmarks.getJsonArray(type);
        for (int i = 0, n = bookmarkArray.size(); i < n; i++) {
            JsonObject bookmark = bookmarkArray.getJsonObject(i);
            result.add(bookmark.getString("url"));
        }
    }

    return result;
}
```
Wenn Du jetzt `getAllUrls(bookmarks, "search", "programming")` aufrufst, wird über die beiden angegebenen Schlüssel mit der äußeren Schleife iteriert. Für jeden Schlüssel wird das zugehörige JsonArray abgerufen. Mit der inneren Schleife wird über alle JsonObjects des gerade betrachteten JsonArrays iteriert und dabei jeweils der Eintrag mit dem Schlüssel "url" abgerufen und zum Ergebnis hinzugefügt. Das entspricht genau der Struktur oben.


----------



## Tueftler (10. Jan 2020)

Zunächst mal ein Danke schön an MoxxiManagarm und Mihe7, ihr habt mich auf die richtige Schiene gesetzt. Zum Schluss noch eine vielleicht blöde Frage: Das ganze JSON ist ein JSONArray. Wenn ich den json-String parse  und das Ergebnis statt zunächst auf JSONObject direkt auf JSONArray caste, bekomme ich eine Laufzeit-Fehlermeldung "Unexpected token END OF FILE at position 2.".
Wenn ich auf JSONObject caste, fehlt mir der Name, um mit einem get das Array aus dem Object rauszuholen und iterieren lässt sich das JSONObject wegen fehlendem Iterator auch nicht. Wie aber komme ich sonst an das Array ran?


----------



## mihe7 (10. Jan 2020)

Tueftler hat gesagt.:


> Wenn ich den json-String parse und das Ergebnis statt zunächst auf JSONObject direkt auf JSONArray caste, bekomme ich eine Laufzeit-Fehlermeldung "Unexpected token END OF FILE at position 2.".


Ein Cast ist lediglich ein "Hinweis" an den Compiler, für ein Objekt einen bestimmten Typ anzunehmen. Zur Laufzeit kannst Du dadurch höchstens eine ClassCastException bekommen.


----------

