web-dev-qa-db-fra.com

REST: comment sérialiser un objet Java en JSON de manière "superficielle"?

Supposons que j’ai les entités JPA suivantes:

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

Spring et Java EE ont tous deux REST implémentations avec des sérialiseurs par défaut, qui acheminent les entités de/vers JSON sans codage supplémentaire. Toutefois, lors de la conversion de Outer en JSON, Spring et EE imbriquent une copie complète de Inner:

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

C'est un comportement correct mais problématique pour mes services Web, car les graphiques d'objets peuvent être complexes/profonds et peuvent contenir des références circulaires. Existe-t-il un moyen de configurer le marshaller fourni pour rassembler les POJO/entités de manière "superficielle" sans avoir à créer un sérialiseur JSON personnalisé pour chacun d'eux? Un sérialiseur personnalisé qui fonctionne sur toutes les entités conviendrait. J'aimerais idéalement quelque chose comme ceci:

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

Je voudrais aussi que cela «annule» le JSON dans l'objet Java équivalent. Félicitations si la solution fonctionne avec Spring et Java EE. Merci!

14
Alvin Thompson

Après de nombreux problèmes, je donne raison à Cássio Mazzochi Molin en déclarant que "L’utilisation de la persistance d’entités dans votre REST API ne peut être une bonne idée"

Je ferais que la couche de gestion transforme les entités de persistance en DTO. 

Vous pouvez le faire très facilement avec des bibliothèques comme mapstruct

Si vous voulez toujours continuer avec cette mauvaise pratique vous pouvez utiliser jackson et personnaliser votre mappeur jackson

2

Pour déchiffrer des graphiques d'objets complexes à l'aide de jaxb @XmlID et @XmlIDREF est fait.

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

Cela se traduira par le json-fragment suivant 

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

Si vous annulez ce json, vous vous assurerez que detail et detailAgain sont les mêmes instances. 

Les deux annotations font partie de jaxb, donc cela fonctionnera aussi bien au printemps qu’en Java EE. Marshalling to json ne fait pas partie de la norme, donc j'utilise moxy dans l'exemple.

Mettre à jour

L'utilisation explicite de moxy n'est pas nécessaire dans une ressource JAX-RS. La copie suivante s’exécute parfaitement sur un conteneur Java-EE-7 (glassfish 4.1.1) et produit le fragment json ci-dessus:

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

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

J'ai eu le même problème et j'ai fini par utiliser des annotations jackson sur mes entités pour contrôler la sérialisation:

Ce dont vous avez besoin est @JsonIdentityReference (alwaysAsId = true) pour indiquer au sérialiseur de bean que cette référence ne doit être qu'un identifiant. Vous pouvez voir un exemple sur mon 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; 

Si vous souhaitez un contrôle complet de la façon dont vos entités sont représentées en tant que JSON, vous pouvez utiliser JsonView pour définir quel champ est sérialisé en fonction de votre vue.

@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

À votre santé !

1
sashok_bg

Parcourez la bibliothèque FLEXJSON pour inclure/exclure intelligemment la hiérarchie des classes imbriquées lors de la sérialisation des objets Java.

Exemples pour flexjson.JSONSerializer présenté ici

0
Akash Mishra

pour ce problème Il y a deux solutions. 1-using Jackson View 2- Créer deux classes de mappage pour une entité innner. l'un d'eux inclut des champs personnalisés et un autre inclut tous les champs ...

je pense que Jackson Json View est une meilleure solution ...

0
yousef