2016-07-27 45 views
13

Załóżmy, że mam następujące podmioty JPA:REST: jak serializować obiekt Java do JSON w "płytki" sposób?

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

Zarówno wiosną i Java EE mają implementacje REST z domyślnymi serializers które będą Marshall podmioty do/od JSON bez dalszego kodowania. Ale podczas konwersji Outer do JSON, zarówno wiosną i EE gniazdem pełną kopię Inner w nim:

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

Jest to poprawne zachowanie, ale problematyczne dla moich usług internetowych, ponieważ wykresy obiektów można uzyskać głęboki/złożona i może zawierać odwołania kołowe. Czy istnieje sposób na skonfigurowanie dostarczonego elementu Marshall, aby zamiast niego uporządkować obiekty POJO/podmioty "płytko", bez potrzeby tworzenia niestandardowego serializatora JSON dla każdego z nich? Jeden niestandardowy serializator, który działa na wszystkich elementach, byłby w porządku. Ja bym idealnie jak coś takiego:

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

ja też jak to się „wycofać” JSON z powrotem do równoważnych obiektu java. Dodatkowe bonusy, jeśli rozwiązanie działa zarówno w trybie Spring, jak i Java EE. Dzięki!

+1

Dlatego używając jednostki utrwalania w REST API nie może być dobrym pomysłem. Spójrz na to [odpowiedź] (http://stackoverflow.com/a/36175349/1426227). Czy korzystanie z dostosowanych DTO jest opcją? –

+1

@ CássioMazzochiMolin: Podoba mi się podejście, ale projekt jest już całkiem dojrzały. W tym momencie nie jest praktyczne przełączanie wszystkich istniejących usług (i ich klientów) na używanie DTO zamiast jednostek i nie chcę "mieszać i dopasowywać". –

Odpowiedz

-1

Możesz odłączyć jednostkę JPA przed serializacją, jeśli użyjesz leniwego ładowania, unikaj ładowania obiektów podrzędnych.

Innym sposobem, ale jest zależny od API serializatora JSON, można użyć "przejściowej" lub specyficznej adnotacji.

Why does JPA have a @Transient annotation?

Zły sposobem jest użycie narzędzia jak spycharki skopiować obiekt WZP w innej klasie tylko właściwości potrzebne do JSON (ale działa ... niewielki narzut pamięci, procesora i czasu ...)

@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

Próbowałem czegoś podobnego, ale jeśli odłączysz obiekt przed serializacją, to nie będzie on serializować i zamiast tego po prostu wyrzuci wyjątek "encji odłączony", gdy serialalizer spróbuje uzyskać dostęp do 'wewnętrznego'. Pomyślałem również o użyciu '@ JsonIgnore' na' inner' i dodaniu osobnego pola 'innerId', podobnego do tego, co sugerujesz, ale wymagałoby to wiele kodu standardowego dla każdej klasy, aby zachować synchronizację. –

+0

ok pierwszy sposób nie działa. Przepraszam. EclipseLink daje jej rozwiązanie http://www.eclipse.org/eclipselink/documentation/2.6/solutions/restful_jpa002.htm Korzystanie mappedBy uniknąć odniesień koliste i używając odniesienia (_link) dostarczenie url zagnieżdżonych obiektów –

+0

niestety 'mappedBy' pozwala określić, gdzie relacja jest zdefiniowana w WZP, ale nie ma wpływu na rzeczywiste obiekty. –

2

rozszyfrować złożonych wykresów obiektu przy użyciu JAXB @XmlID i @XmlIDREF jest właśnie warunkach.

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

Spowoduje następującym JSON fragment

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

Unmarshalling niniejszego json zapewni detail i detailAgain są takie same przypadki.

Dwie adnotacje są częścią jaxb, więc będzie działać zarówno na wiosnę, jak iw java EE. Marshalling to json nie jest częścią standardu, więc używam moxy w tym przykładzie.

Aktualizacja

jawnie przy użyciu Moxy nie jest konieczne w JAX-RS Resource. Poniżej wycięte doskonale działa na język Java EE-7 pojemnika (przeźroczkom 4.1.1), co prowadzi do wyżej json fragmentu:

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

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

To podejście wymagałoby ręcznego utworzenia elementu jaxb marshaller w każdym punkcie końcowym i użyj tego zamiast wbudowanego w Spring/EE modułu Marshall, który ma wiele dodatkowych elementów. Jak wspomniano, zarówno Java EE, jak i Spring automatycznie przesyłają/usuwają obiekty w punktach końcowych bez dodatkowego kodu, więc chciałbym to wykorzystać. Cała kwestia polega na uniknięciu tego rodzaju szablonu - w przeciwnym razie po prostu utworzę niestandardowego koordynatora Jackson dla każdego obiektu. –

+0

Innymi słowy, nie chcę zaśmiecać moich punktów końcowych dodatkowym kodem - szczególnie nie przy kodzie, który nie jest bezpośrednio związany z wykonywanym zadaniem. Nie chcę też tworzyć/rejestrować niestandardowych koordynatorów dla każdej klasy, ponieważ utrzymywanie ich w synchronizacji z obiektami, które ewoluują, byłoby bólem głowy. Po prostu chcę zmienić zachowanie dostarczonego programu marshaller tak, aby nie osadzał obiektów zagnieżdżonych, a zamiast tego po prostu podaje ich identyfikator. –

+0

Korzystanie z funkcji moxy bezpośrednio jest artefaktem tego prostego przykładu. Jax-RS jest w stanie przeprowadzić ładowanie do json domyślnie. Nie musisz ręcznie tworzyć osobliwego elementu jaxb. Wystarczy dodać adnotacje '@ XmlID' i' @ XmlIDREF' do swoich encji. – frifle

1

po wielu problemów I uzasadniają Cássio Mazzochi Molin mówiąc, „THE użycie trwałości jednostek w twoim interfejsie API REST nie może być dobrym pomysłem "

Zrobiłbym to, że warstwa biznesowa przekształci obiekty trwałości w DTO.

Można to zrobić bardzo łatwo z bibliotekami jak mapstruct

Jeśli nadal chcesz kontynuować ten złej praktyki można użyć jackson i customize your jackson mapper

+0

Jak już wspomniałem, wolałbym nie tworzyć niestandardowego serializera dla każdej jednostki. Ponieważ mamy wiele podmiotów i często się zmieniają, byłby to ból głowy związany z utrzymaniem. –

+0

Korzystanie z biblioteki, o której wspomniałem, nie stanowi problemu. Mapper jest tworzony przez adnotacje. Jedyne, co powinieneś zrobić, to przepisać adnotacje. Nadal, do czego służy biblioteka Marshalla? czy możesz użyć Jacksona? –

+0

Miałem na myśli Twoją propozycję dostosowania programu do mapowania jacksona. DTO to dobre podejście, ale w tym momencie jest już za późno, aby zmienić kod, aby z nich korzystać, więc Mapstruct nie działa. –

0

miałem ten sam problem i skończyło się przy użyciu adnotacji jackson w moich jednostkach w celu kontrolowania serializacji:

Potrzebna jest funkcja @JsonIdentityReference (alwaysAsId = true) do instruowania serializacji komponentów bean er, że to odniesienie powinno być tylko identyfikatorem. Możesz zobaczyć przykład na moim repo:

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; 

Jeśli chcesz pełną kontrolę w jaki sposób podmioty są reprezentowane jako JSON, można użyć JsonView aby określić, które pole jest szeregowane związane z widoku .

@JsonView (Views.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

Cheers!

+0

Nie działa to z wersją Spring lub Java EE ... –

0

dla tego problemu Istnieją dwa rozwiązania. 1-używanie json json widoku 2- Tworzenie dwóch klas odwzorowania dla obiektu innner. jeden z nich zawiera pól niestandardowych, a drugi zawiera wszystkie pola ...

myślę widok Jackson JSON jest lepszym rozwiązaniem ...

+0

Oba zostały już wymienione. –

0

przejść przez bibliotekę FLEXJSON do elegancko włączenia/wyłączenia zagnieżdżone klasy hierarchii podczas szeregowania Java obiekty.

Przykłady flexjson.JSONSerializer przedstawiony here

+0

jak wspomniano w pytaniu, nie chcę tworzyć niestandardowego serializera dla każdego obiektu. –

+0

Zdecydowanie uważam, że możesz stworzyć niestandardowy serializator w jednym miejscu, dzięki czemu rozszerzy on 'JSONSerializer' wprowadzając wyrażenie wieloznaczne i nadpisuje metodę' serialize() '. Nie określaj użycia metody 'include()'. Pomoże to zagwarantować, że 'serializer' nie będzie zawierał zagnieżdżonych klas w dowolnym momencie. Następnie możesz spróbować wywołać to, aby serializować różne obiekty. Spróbuj włączyć to, zignoruj, jeśli nadal nie rozwiązuje twojego celu. –