# JSON flatten und wieder in DTO konvertieren



## Avalon (9. Feb 2021)

Hallo, ich habe eine klassische Spring Boot MVC Anwendung und versuche dort gerade ein JSON Objekt, das aus 3 verschachtelten DTOs besteht, zu flatten und wieder in ein DTO zu konvertieren. (Jackson, JsonFlattener, mapstruct). Aber wie zum Geier, kann ich aus dem Flat JSON wieder ein DTO bauen. Es hat zwar schon funktioniert. Für den User sind die Values auch drin. Aber für die eingebetten Collections, fehlen die Werte (null). Ist eigentlich auch klar, weil die unbekannt und nicht gemappt sind. Aber wie kann man das mit mapstruct mappen (in die Richtig geht das doch, oder?). Alles was ich ich jetzt bei Google gefunden habe, hat mir leider nicht weiter geholfen. jsonschema2pojo habe ich bemüht. `objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);` brachte auch nicht das gewünschte Ergebnis. Ich weiß nicht mehr weiter.
[CODE lang="java" title="UserDto"]@Getter @Setter
public class UserDto {

    private Long id;

    @NotNull
    @NotEmpty
    private String firstname;

    @NotNull
    @NotEmpty
    private String lastname;

    @NotNull
    @NotEmpty
    private String username;

    @NotNull
    @NotEmpty
    private String email;

    @NotNull
    @NotEmpty
    private String password;
    private String passwordconfirm;
    private boolean enabled;
    private boolean tokenexpired;
    private Collection<RoleDto> roles;
}[/CODE]
[CODE lang="java" title="RoleDto"]@Getter @Setter
public class RoleDto {

    private String name;
    private Collection<PrivilegeDto> privileges;
}[/CODE]
[CODE lang="java" title="PrivilegeDto"]@Getter @Setter
public class PrivilegeDto {
    private Long id;
    private String name;

}[/CODE]

Bis zum Flat JSON bin ich gekommen. Das liegt als String vor und sieht so aus...
[CODE lang="java" title="UserDto JSON Flat als String"]{
    "id": 1,
    "firstname": "Test",
    "lastname": "Test",
    "username": "Avalon",
    "email": "test@test.com",
    "password": "$2a$10$.zywwDazLvC9srdDWuhp3eAvlVpolETRpUY4hQ4soq.j0Jsygu.gy",
    "passwordconfirm": null,
    "enabled": true,
    "tokenexpired": false,
    "roles[0].name": "ROLE_ADMIN",
    "roles[0].privileges[0].id": 1,
    "roles[0].privileges[0].name": "READ_PRIVILEGE",
    "roles[0].privileges[1].id": 2,
    "roles[0].privileges[1].name": "WRITE_PRIVILEGE"
}[/CODE]


----------



## Oneixee5 (9. Feb 2021)

```
<dependency>
    <groupId>com.github.wnameless</groupId>
    <artifactId>json-flattener</artifactId>
    <version>????</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>????</version>
</dependency>
```


```
String nestedJson = JsonUnflattener.unflatten(flattenedJson);
ObjectMapper objectMapper = new ObjectMapper();
UserDto dto = objectMapper.readValue(nestedJson, UserDto.class);
```


----------



## Avalon (9. Feb 2021)

Da hab ich mich wohl falsch ausgedrückt. Wenn ich den geflatteten JSON String in `String nestedJson = JsonUnflattener.unflatten(flattenedJson);` reinpacke, kommt ein ungeflattetes Object raus. Das will ich ja nicht. Das sieht dann bei mir so aus.
[CODE lang="json" title="unflattes nested JSON"]{
    "id": 1,
    "firstname": "Test",
    "lastname": "Test",
    "username": "Avalon",
    "email": "test@test.com",
    "password": "$2a$10$.zywwDazLvC9srdDWuhp3eAvlVpolETRpUY4hQ4soq.j0Jsygu.gy",
    "passwordconfirm": null,
    "enabled": true,
    "tokenexpired": false,
    "roles": [
        {
            "name": "ROLE_ADMIN",
            "privileges": [
                {
                    "id": 1,
                    "name": "READ_PRIVILEGE"
                },
                {
                    "id": 2,
                    "name": "WRITE_PRIVILEGE"
                }
            ]
        }
    ]
}[/CODE]


----------



## Oneixee5 (9. Feb 2021)

Wozu soll das flatten und unflatten gut sein? So etwas habe ich noch nie benötigt.


----------



## Avalon (9. Feb 2021)

Das ist eine andere Frage. Ich hab da auch googln müssen (Key:Value, Sieht schöner aus, Belastet den Traffic weniger, die Datenbank usw.). Soweit ich das mitbekommen habe, ist das nur ab bestimmten Größenordnungen wichtig. Ist aber auch egal. Ich muss das abliefern, sonst Kopf kürzer :-D


----------



## Oneixee5 (9. Feb 2021)

Schade, es hat mich wirklich interessiert. Dein "ungeflattetes Object" sieht doch gut aus. Einfach in des DTO umwandeln und fertig, s.o.


----------



## Avalon (9. Feb 2021)

Also versuch nochmal anhand einer Zeile zu beschreiben was ich brauche. Wenn ich den oben beschrieben Flat String wieder in ein DTO Object konvertiere und die flache Struktur erhalten bleiben soll, erhalte ich in der Zeile 
	
	
	
	





```
"roles[0].name": "ROLE_ADMIN",
```
einen "null" Wert. Also 
	
	
	
	





```
"roles[0].name": "null",
```
. Ich muss es irgendwie schaffen, mittels mapstruct oder wie auch immer den Wert 
	
	
	
	





```
"ROLE_ADMIN"
```
 auf 
	
	
	
	





```
"roles[0].name"
```
 zu mappen. Das ist zwar auch falsch ausgedrückt. Aber hoffe jemand weiß was ich meine. Mit der "Fachlichkeit" hab ich es nicht so.


----------



## mrBrown (9. Feb 2021)

Was hast du denn genau und was genau brauchst du?

Hast du Java-DTOs, und brauchst "flatted JSON"? Dann DTO mit Jackson zu JSON und den Json-String dann flatten.
Oder hast du "flatted JSON"? Dann unflatten und den String mit Jackson normal deserialisieren.


BTW: das DTO sollte in der Form *besser* niemals benutzt werden. `password`, `passwordconfirm`, `enabled`, `tokenexpired` und `roles` sollten niemals zusammen in einem DTO stehen, damit provoziert man nur Datenleaks und Sicherheitslücken.



Avalon hat gesagt.:


> Key:Value, Sieht schöner aus, Belastet den Traffic weniger, die Datenbank usw.). Soweit ich das mitbekommen habe, ist das nur ab bestimmten Größenordnungen wichtig.


"geflattet" ist es größer, in deinem Beispiel sind's grob 20% (keine Ahnung, vielleicht gib's auch Fälle in denen es kleiner ist, aber leserlicher wird's damit garantiert nicht), Traffic ist also eher weniger 'n Argument.



Avalon hat gesagt.:


> Ich muss das abliefern, sonst Kopf kürzer :-D


Ich hoffe, das ist keine Ausbildung oä


----------



## mrBrown (9. Feb 2021)

Avalon hat gesagt.:


> Also versuch nochmal anhand einer Zeile zu beschreiben was ich brauche. Wenn ich den oben beschrieben Flat String wieder in ein DTO Object konvertiere und die flache Struktur erhalten bleiben soll, erhalte ich in der Zeile


In einem DTO gibts keine flache Struktur, wie soll die da erhalten bleiben?


----------



## Avalon (9. Feb 2021)

Das hab ich jetzt bei einem GET-Request auf `http://localhost:8080/user/1`

```
{
    "id": 1,
    "firstname": "Test",
    "lastname": "Test",
    "username": "Avalon",
    "email": "test@test.com",
    "password": "$2a$10$.zywwDazLvC9srdDWuhp3eAvlVpolETRpUY4hQ4soq.j0Jsygu.gy",
    "passwordconfirm": null,
    "enabled": true,
    "tokenexpired": false,
    "roles": [
        {
            "name": "ROLE_ADMIN",
            "privileges": [
                {
                    "id": 1,
                    "name": "READ_PRIVILEGE"
                },
                {
                    "id": 2,
                    "name": "WRITE_PRIVILEGE"
                }
            ]
        }
    ]
}
```
Und so soll es aussehen (und auch ein Object sein)


```
{
    "id": 1,
    "firstname": "Test",
    "lastname": "Test",
    "username": "Avalon",
    "email": "test@test.com",
    "password": "$2a$10$.zywwDazLvC9srdDWuhp3eAvlVpolETRpUY4hQ4soq.j0Jsygu.gy",
    "passwordconfirm": null,
    "enabled": true,
    "tokenexpired": false,
    "roles[0].name": "ROLE_ADMIN",
    "roles[0].privileges[0].id": 1,
    "roles[0].privileges[0].name": "READ_PRIVILEGE",
    "roles[0].privileges[1].id": 2,
    "roles[0].privileges[1].name": "WRITE_PRIVILEGE"
}
```

Geht das irgendwie?


----------



## mrBrown (9. Feb 2021)

Hier:


mrBrown hat gesagt.:


> Hast du Java-DTOs, und brauchst "flatted JSON"? Dann DTO mit Jackson zu JSON und den Json-String dann flatten.



Vielleicht gibt's irgendeine Lib für Spring, die das automatisch macht, aber keine Ahnung ob sowas existiert. Alternativ könnte man sicherlich nen eigenen MediaType nutzen und 'nen MessageConverter dafür schreiben, dann hat man die Logik dafür gekapselt und vom eigentlichen Code getrennt.


----------



## Avalon (9. Feb 2021)

Ich hab bis jetzt leider nichts gefunden. Es gibt hunderte Tutorials, wie man das mit "normalen" DTOs macht die verschachtelt sind. Das ist sogar richtig einfach. Aber in keinem davon sind Collections, Listen oder Sets in den Klassenvariablen. Ich weiß einfach nicht, wie ich in Mapstruct "roles[0].name" den Wert "ROLE_ADMIN" zuweisen bzw. ihn mappen kann. Der steckt bei Collection<RoleDto> mit drin.


----------



## mrBrown (9. Feb 2021)

An welcher Stelle soll dabei überhaupt Mapstruct zum Einsatz kommen? Mit Mapstruct mappt man *zwischen Java-Klassen*, in deinem Fall willst du aber von JSON auf die Java-Klasse mappen.

Und wie flatted-Json -> unflatted-Json -> DTO möglich ist, steht in #2:


Oneixee5 hat gesagt.:


> ```
> <dependency>
> <groupId>com.github.wnameless</groupId>
> <artifactId>json-flattener</artifactId>
> ...


----------



## mrBrown (9. Feb 2021)

@Avalon: Kannst du noch mal ganz einfach mit 4 Wörtern sagen, *was genau du an Daten hast* (Java-Entity, Java-DTO, Json, flatted-Json, ...) und *was genau du an Daten brauchst* (Java-Entity, Java-DTO, Json, flatted-Json, ...)? Einfach nur ein "Habe: X, brauchte: Y"


----------



## Avalon (9. Feb 2021)

Ich schieb hier einfach den wichtigen Teil aus meinem Controller ungefiltert rein. ACHTUNG !!!! BAUSTELLE!!! So hat es angefangen mit dem Fehler

"There was an unexpected error (type=Internal Server Error, status=500).
Unrecognized field "roles[0].name" (class de.pixpartout.my.dto.UserDto), not marked as ignorable (10 known properties: "lastname", "passwordconfirm", "username", "id", "email", "roles", "enabled", "firstname", "tokenexpired", "password"]) at [Source: (String)""


```
@GetMapping(path = "/user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public UserDto getById(@PathVariable Long id)
            throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        //objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        UserDto userDto = userMapper.mapToDto(userService.getByID(id));       
        
        //Json Objekt zu String konvertieren
        String json = objectMapper.writeValueAsString(userDto);
        System.out.println("#######################");
        System.out.println(json);
        System.out.println("#######################");
        
        //FlattenJson aus String erstellen
        String flattenJson = JsonFlattener.flatten(json);
        //Map<String, Object> flattenJson = JsonFlattener.flattenAsMap(json);
        System.out.println("#######################");
        System.out.println(flattenJson);
        System.out.println("#######################");
        
        //FlattenJson wieder in Object konvertieren
        userDto = objectMapper.readValue(flattenJson, UserDto.class);
        
        return userDto;
    }
```


----------



## mrBrown (9. Feb 2021)

Avalon hat gesagt.:


> Ich schieb hier einfach den wichtigen Teil aus meinem Controller ungefiltert rein.


Nein, schreib es bitte mit Worten.

Der Controller ist Mist, und Anfangen aus Mist zu lesen, was du eigentlich willst, wollen wir hier nicht


----------



## Avalon (9. Feb 2021)

mrBrown hat gesagt.:


> Der Controller ist Mist, und Anfangen aus Mist zu lesen, was du eigentlich willst, wollen wir hier nicht


Chef? Bist Du das?  Kommt mir alles irgendwie bekannt vor. 


OK. Ich hab erstmal alles in den Controller reingepackt. Wenn es funktioniert wollte ich es auslagern. Aber in Worten beschreiben...puuh, ich muss überlegen? Ich fang mal beim Urschleim an. Eigentlich alles Standard (Spring Boot REST). Meine Packages in Eclipse sind Entity, Controller, Service, Mapper, Repository und DTO. Alles läuft über JPA/Hibernate und MariaDB.  Bei einem Get Request wird die entsprechende Methode in der ServiceImplementation aufgerufen, die holt die Daten aus der Datenbank mappt das Ganze per Mapstruct auf ein DTO (normalerweise nur die benötigten Properties) und gibt das DTO zurück an den Clienten. Fehlt noch was? Der Controller sah bis jetzt so aus.


```
@GetMapping(path = "/user/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public UserDto getById(@PathVariable Long id) {
        return userMapper.mapToDto(userService.getByID(id));
    }
```
Im zurückgegeben JSON Object UserDto sind weitere DTOs. Collection<RoleDto> und Collection<PrivilegeDto> mit den dazugehörigen Werten. Das sieht dann so aus.

```
{
    "id": 1,
    "firstname": "Test",
    "lastname": "Test",
    "username": "Avalon",
    "email": "test@test.com",
    "password": "$2a$10$.zywwDazLvC9srdDWuhp3eAvlVpolETRpUY4hQ4soq.j0Jsygu.gy",
    "passwordconfirm": null,
    "enabled": true,
    "tokenexpired": false,
    "roles": [
        {
            "name": "ROLE_ADMIN",
            "privileges": [
                {
                    "id": 1,
                    "name": "READ_PRIVILEGE"
                },
                {
                    "id": 2,
                    "name": "WRITE_PRIVILEGE"
                }
            ]
        }
    ]
}
```
Und dieses JSON Object ist der aktuelle Auslieferungszsutand an den Clienten, soll aber bevor es an den Client ausgeliefert wird, geflattet werden.
Ziel: Der Client soll ein JSON Object (Userdaten samt Rollen und Privilegien) bekommen, nur mit Key:Value Paaren, ohne Arrays. 


```
{
    "id": 1,
    "firstname": "Test",
    "lastname": "Test",
    "username": "Avalon",
    "email": "test@test.com",
    "password": "$2a$10$.zywwDazLvC9srdDWuhp3eAvlVpolETRpUY4hQ4soq.j0Jsygu.gy",
    "passwordconfirm": null,
    "enabled": true,
    "tokenexpired": false,
    "roles[0].privileges[0].id": 1,
    "roles[0].privileges[0].name": "READ_PRIVILEGE",
    "roles[0].privileges[1].id": 2,
    "roles[0].privileges[1].name": "WRITE_PRIVILEGE",
    "roles[0].name": "ROLE_ADMIN"
}
```


----------



## mrBrown (9. Feb 2021)

Avalon hat gesagt.:


> Chef? Bist Du das?


Ne, dann hättest du sinnvolle Aufgaben bekommen 


Avalon hat gesagt.:


> Und dieses JSON Object ist der aktuelle Auslieferungszsutand an den Clienten, soll bevor es an den Client ausgeliefert wird, geflattet werden.


Okay 

Der "stumpfeste Weg" wäre, das Json-Objekt selbst in der Controller-Methode zu erzeugen und zu flatten, etwa so:


```
UserDto dto = userMapper.mapToDto(userService.getByID(id));
String json = objectMapper.writeValueAsString(dto);
return JsonFlattener.flatten(json);
```

Der return-Type muss dann String werden.


_Theoretisch_ kann man sicherlich irgendwie das Flatten auslagern, damit der Controller weiterhin schön ist und nur ein UserDto zurück gibt.
Möglich wäre da entweder ein eigener MediaType (kommt auf den Client an, ob das geht) oder ein Wrapper (sowas wie `class Flat<T> {...}`), und dafür registriert man dann einen eigenen HttpMessageConverter, der das ganze zu flatted Json serialisiert (und nötigenfalls deserialisiert).


----------



## Avalon (9. Feb 2021)

OK Danke. Den stumpfen Weg hatte ich schon. Aber den return-type als String zu übergeben bzw. zu belassen, daran habe ich jetzt nicht gedacht, weil Objekt Vorgabe war. Mal sehen ob man das so lassen kann. Hoffentlich war das nicht Sinn und Zweck der Aufgabe. Darauf zu kommen!  Trotzdem Danke. Jetzt muss ich das alles nur noch in ein Thymeleaf Template unterbringen, was meine css Datei nicht im Thymeafstandardordner für css Dateien findet. Und fertig.


----------



## mrBrown (9. Feb 2021)

Avalon hat gesagt.:


> OK Danke. Den stumpfen Weg hatte ich schon. Aber den return-type als String zu übergeben bzw. zu belassen, daran habe ich jetzt nicht gedacht, weil Objekt Vorgabe war. Mal sehen ob man das so lassen kann. Hoffentlich war das nicht Sinn und Zweck der Aufgabe. Darauf zu kommen!


Gibts noch andere Vorgaben, die du uns bisher verschwiegen hast?  Je besser man die eigentlichen Anforderungen kennt, desto besser kann man helfen – und gleichzeitig: je mehr jemand, der Hilfe braucht, selbst die Anforderungen versucht zu interpretieren, desto schlechter kann man helfen 

Wenn zB alle Endpunkte immer flatted Json zurückgeben sollen, könnte man das an einer Stelle einstellen, und die Controller geben nur die DTOs direkt zurück.
Oder vielleicht sollen auch konkrete DTOs so zurückgegeben werden, dann könnte man die DTOs entsprechende markieren.
Oder vielleicht sollen auch nur bestimmte Endpunkte flatted Json zurückgeben, dann könnte man über Wrapper-Typen nachdenken.
Oder vielleicht soll auch der Client das entscheiden, dann wäre MediaTypes uU ein guter Ansatz.


Und um das noch mal zu wiederholen: ein DTO in der Form sollte *nicht* existieren. Vielleicht war auch der ganze Sinn der Aufgabe, etwas gegen dieses DTO zu unternehmen


----------



## Avalon (9. Feb 2021)

mrBrown hat gesagt.:


> Wenn zB alle Endpunkte immer flatted Json zurückgeben sollen, könnte man das an einer Stelle einstellen, und die Controller geben nur die DTOs direkt zurück.
> Oder vielleicht sollen auch konkrete DTOs so zurückgegeben werden, dann könnte man die DTOs entsprechende markieren.
> Oder vielleicht sollen auch nur bestimmte Endpunkte flatted Json zurückgeben, dann könnte man über Wrapper-Typen nachdenken.
> Oder vielleicht soll auch der Client das entscheiden, dann wäre MediaTypes uU ein guter Ansatz.
> ...


An all das hab ich auch schon gedacht... Aaaaaaaaber DAAAAAS


mrBrown hat gesagt.:


> Gibts noch andere Vorgaben, die du uns bisher verschwiegen hast?  Je besser man die eigentlichen Anforderungen kennt, desto besser kann man helfen – und gleichzeitig: je mehr jemand, der Hilfe braucht, selbst die Anforderungen versucht zu interpretieren, desto schlechter kann man helfen


muss ich erstmal sacken lassen und dafür meinen Babel Fish füttern.


----------



## Avalon (9. Feb 2021)

Ich habs! 42  !!!! Ups. Der Wein ist schuld.


----------

