2016-07-27 11 views
13

Angenommen habe ich die folgenden JPA-Entitäten:REST: Wie kann man ein Java-Objekt "seicht" zu JSON serialisieren?

@Entity 
public class Inner { 
    @Id private Long id; 
    private String name; 

    // getters/setters 
} 

@Entity 
public class Outer { 
    @Id private Long id; 
    private String name; 
    @ManyToOne private Inner inner; 

    // getters/setters 
} 

Sowohl Frühling und Java EE haben REST-Implementierungen mit Standard-Serializer die Will Marshall die Entitäten zu/von JSON ohne weitere Codierung. Aber wenn Outer zu JSON Konvertierung sowohl Frühlings- und EE Nest eine vollständige Kopie der Inner in ihm:

// Outer 
{ 
    "id": "1234", 
    "name": "MyOuterName", 
    "inner": { 
    "id": "4321", 
    "name": "MyInnerName" 
    } 
} 

Dies ist das richtige Verhalten aber problematisch für meine Web-Services, da die Objektgraphen bekommen tief/komplex und enthalten kreisförmige Referenzen. Gibt es eine Möglichkeit, den bereitgestellten Marshaller so zu konfigurieren, dass er die POJOs/Entitäten auf eine "flache" Weise marshallen lässt, ohne dafür einen eigenen JSON-Serializer erstellen zu müssen? Ein benutzerdefinierter Serializer, der auf allen Entitäten funktioniert, wäre in Ordnung. Ich würde im Idealfall wie etwa wie folgt:

// Outer 
{ 
    "id": "1234", 
    "name": "MyOuterName", 
    "innerId": "4321" 
} 

Ich würde auch, wie es zu „unmarshall“ die JSON zurück in das entsprechende Java-Objekt. Bonus-Kudos, wenn die Lösung sowohl mit Spring als auch mit Java EE arbeitet. Vielen Dank!

+1

Das ist, warum. Sehen Sie sich dies an (http://stackoverflow.com/a/36175349/1426227). Sind maßgeschneiderte DTOs eine Option? –

+1

@ CássioMazzochiMolin: Ich mag den Ansatz, aber das Projekt ist jetzt ziemlich ausgereift. Es ist an dieser Stelle nicht praktisch, alle vorhandenen Dienste (und ihre Kunden) auf die Verwendung von DTOs anstelle der Entitäten umzustellen, und ich möchte nicht "mixen und anpassen". –

Antwort

-1

Sie können die JPA-Entität vor der Serialisierung trennen. Wenn Sie Lazyloading verwenden, vermeiden Sie das Laden von Unterobjekten.

Eine andere Möglichkeit, aber ist abhängig von der JSON-Serializer-API, können Sie "transient" oder spezifische Annotation verwenden.

Why does JPA have a @Transient annotation?

Eine schlechte Art und Weise ist Werkzeug wie Planierraupe zu verwenden, um mit JPA Objekt in einer anderen Klasse zu kopieren nur die Eigenschaften müssen für json (aber es funktioniert ... wenig Overhead-Speicher, CPU und Zeit ...

)
@Entity 
public class Outer { 
    @Id private Long id; 
    private String name; 
    @ManyToOne private Inner inner; 
    //load manually inner.id 
    private final Long innerId; 
    // getters/setters 
} 
+0

Ich habe versucht, etwas Ähnliches, aber wenn Sie die Entität vor der Serialisierung trennen, wird es nicht serialisieren und stattdessen nur eine "Entity getrennt" Ausnahme werfen, wenn der Serializer versucht, auf "innere" zuzugreifen. Ich dachte auch darüber nach, '@ JsonIgnore' auf' inner' zu verwenden und ein separates 'innerId'-Feld hinzuzufügen, ähnlich dem, was Sie vorschlagen, aber das würde eine Menge Standardcode für jede Klasse erfordern, um sie synchron zu halten. –

+0

ok erster Weg funktioniert nicht. Es tut uns leid. EclipseLink gibt seine Lösung http://www.eclipse.org/eclipselink/documentation/2.6/solutions/restful_jpa002.htm Verwenden Sie mappedBy, um zirkuläre Verweise zu vermeiden, und verwenden Sie Verweis (_link), um URL von verschachtelten Objekten –

+0

leider "mappedBy" bereitzustellen Hier können Sie angeben, wo die Beziehung in JPA definiert ist, aber nicht die tatsächlichen Objekte. –

2

Um komplexe Objektgraphen mit jaxb @XmlID und @XmlIDREF wird entwirren.

public class JSONTestCase { 

@XmlRootElement 
public static final class Entity { 
    private String id; 
    private String someInfo; 
    private DetailEntity detail; 
    @XmlIDREF 
    private DetailEntity detailAgain; 

    public Entity(String id, String someInfo, DetailEntity detail) { 
     this.id = id; 
     this.someInfo = someInfo; 
     this.detail = detail; 
     this.detailAgain = detail; 
    } 

    // default constructor, getters, setters 
} 

public static final class DetailEntity { 
    @XmlID 
    private String id; 
    private String someDetailInfo; 

    // constructors, getters, setters 
} 

@Test 
public void testMarshalling() throws JAXBException { 
    Entity e = new Entity("42", "info", new DetailEntity("47","detailInfo")); 

    JAXBContext context = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[]{Entity.class}, null); 
    Marshaller m = context.createMarshaller(); 
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
    m.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json"); 
    m.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false); 

    m.marshal(e, System.out); 
} 
} 

Dies wird

{ 
    "detailAgain" : "47", 
    "detail" : { 
     "id" : "47", 
     "someDetailInfo" : "detailInfo" 
    }, 
    "id" : "42", 
    "someInfo" : "info" 
} 

Unmarshalling dieser json in der folgenden json-Fragment führen wird sichergestellt, dass detail und detailAgain die gleichen Instanzen sind.

Die beiden Anmerkungen sind Teil von jaxb, also wird es sowohl im Frühjahr als auch in Java EE funktionieren. Marshalling nach JSON gehört nicht zum Standard, also verwende ich Moxy im Beispiel.

aktualisieren

Explizit mit moxy ist nicht notwendig in einer JAX-RS Ressource. Die folgenden snipped läuft perfekt auf einen Java-EE-7 Behälter (Glassfish 4.1.1) und die Ergebnisse in dem obigen json-Fragmente:

@Stateless 
@Path("/entities") 
public class EntityResource { 

    @GET 
    @Produces(MediaType.APPLICATION_JSON) 
    public Entity getEntity() { 
     return new Entity("42", "info", new DetailEntity("47","detailInfo")); 
    } 
} 
+0

Dieser Ansatz würde erfordern, dass ich manuell einen jaxb Marshaller in jedem Endpunkt erstellen und Verwenden Sie das anstelle des in Spring/EE eingebauten Marshallers, der eine Menge zusätzlichen Boilerplate ist. Wie bereits erwähnt, mar- gieren sowohl Java EE als auch Spring automatisch Objekte in den Endpunkten ohne zusätzlichen Code, sodass ich das gerne nutzen würde. Der Hauptpunkt der Frage ist, diese Art von Boilerplate zu vermeiden - sonst würde ich nur einen benutzerdefinierten Jackson Marshaller für jedes Objekt erstellen. –

+0

Mit anderen Worten, ich möchte meine Endpunkte nicht mit zusätzlichem Code überladen - besonders nicht mit Code, der nicht direkt mit der Aufgabe in Verbindung steht. Ich möchte auch keine benutzerdefinierten Marshaller für jede Klasse erstellen/registrieren müssen, weil es ein Problem wäre, sie mit den Objekten synchron zu halten, wenn sie sich entwickeln. Ich möchte nur das Verhalten des gelieferten Marshallers ändern, so dass es verschachtelte Objekte nicht einbettet, sondern nur seine ID gibt. –

+0

Die explizite Verwendung von Moxy ist ein Artefakt dieses einfachen Testfalls. Jax-RS kann standardmäßig zu json marshallen. Sie müssen keinen Jaxb Marshaller manuell erstellen. Fügen Sie einfach die Anmerkungen '@ XmlID' und' @ XmlIDREF' zu Ihren Entitäten hinzu. – frifle

1

Nach vielen Problemen, die ich Grund zu Cássio Mazzochi Molin geben sagen, dass „die Die Verwendung der Entitätspersistenz in Ihrer REST-API kann keine gute Idee sein. "

Ich würde tun, dass die Business-Schicht Persistenz-Entitäten in DTO transformiert.

du mapstruct

mit Bibliotheken wie sehr leicht tun können, wenn Sie noch mit dieser schlechten Praxis fortsetzen mögen Sie jackson und customize your jackson mapper

+0

Wie bereits erwähnt, möchte ich keinen benutzerdefinierten Serializer für jede Entität erstellen. Da wir viele Entitäten haben und sie sich oft ändern, wäre das ein Maintenance-Kopfschmerz. –

+0

Mit der Bibliothek, die ich erwähnte, ist das kein Problem. Die Mapper werden durch Anmerkungen erstellt. Das einzige, was Sie tun sollten, ist die Annotationen neu zu schreiben. Noch, was zu Marshall Bibliothek verwenden? Kannst du Jackson benutzen? –

+0

Ich bezog mich auf Ihren Vorschlag für die Anpassung des Jackson Mapper. DTOs sind ein guter Ansatz, aber es ist zu diesem Zeitpunkt zu spät, den Code zu ändern, um sie zu verwenden. Daher ist Mapstruct kein Anfänger. –

0

ich das gleiche Problem hatte und am Ende können Verwenden von Jackson-Annotationen auf meinen Entitäten zum Steuern der Serialisierung:

Was Sie brauchen, ist @JsonIdentityReference (alwaysAsId = true), um die Bean-Serialisierung zu instruieren äh, dass diese Referenz nur eine ID sein sollte. Sie können ein Beispiel auf meinem Repo sehen:

https://github.com/sashokbg/company-rest-service/blob/master/src/main/java/bg/alexander/model/Order.java

@OneToMany(mappedBy="order", fetch=FetchType.EAGER) 
@JsonIdentityReference(alwaysAsId=true) // otherwise first ref as POJO, others as id 
private Set<OrderDetail> orderDetails; 

Wenn Sie eine vollständige Kontrolle darüber, wie Ihre Entitäten wollen als JSON vertreten, Sie JSONView können Sie festlegen, welches Feld zu Ihrer Ansicht im Zusammenhang serialisiert .

@JsonView (Ansichten.Public.class) public int id;

@JsonView(Views.Public.class) 
public String itemName; 

@JsonView(Views.Internal.class) 
public String ownerName; 

http://www.baeldung.com/jackson-json-view-annotation

Prost!

+0

Dies scheint nicht mit Spring oder Java EE zu funktionieren ... –

0

für dieses Problem Es gibt zwei Lösungen. 1-jackson json verwenden Ansicht 2- Erstellung von zwei Mapping-Klassen für innner Einheit. einer von ihnen enthält benutzerdefinierte Felder und eine andere umfasst alle Felder ...

denke ich jackson json Ansicht bessere Lösung ist ...

+0

Beide wurden bereits erwähnt. –

0

Gehen Sie durch die FLEXJSON Bibliothek intelligent schließen/verschachtelte Klassenhierarchie ausschließen, während Java Serialisierung Objekte. mit Ausdauer Entitäten in Ihrem REST-API kann nicht eine gute Idee sein

Beispiele für flexjson.JSONSerializer präsentiert here

+0

Wie ich in der Frage erwähnt habe, möchte ich keinen benutzerdefinierten Serializer für jedes Objekt erstellen müssen. –

+0

Ich denke definitiv, dass Sie einen benutzerdefinierten Serializer an einer Stelle erstellen können, um 'JSONSerializer' zu erweitern, indem Sie einen Wildcard-Ausdruck setzen und die 'serialize()' Methode überschreiben. Geben Sie nicht die Verwendung der 'include()' Methode an. Dadurch wird sichergestellt, dass 'serializer' keine verschachtelten Klassen enthält. Sie können dann versuchen, dies aufzurufen, um verschiedene Objekte zu serialisieren. Versuchen Sie, dies zu integrieren, ignorieren Sie, ob es Ihren Zweck immer noch nicht löst. –