Подтвердить что ты не робот

REST: как сериализовать java-объект JSON в "мелкой" форме?

Предположим, что у меня есть следующие сущности JPA:

@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 и java EE имеют реализации REST с сериализаторами по умолчанию, которые будут маршировать объекты в/из JSON без дальнейшего кодирования. Но при преобразовании Outer в JSON оба Spring и EE вставляют в него полную копию Inner:

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

Это правильное поведение, но проблематично для моих веб-сервисов, поскольку графы объектов могут быть глубокими/сложными и содержать круговые ссылки. Есть ли способ настроить поставляемый маршаллер для маршаллирования POJO/сущностей "мелким" способом вместо того, чтобы не создавать пользовательский сериализатор JSON для каждого из них? Один пользовательский сериализатор, который работает на всех объектах, будет в порядке. В идеале мне бы хотелось что-то вроде этого:

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

Я также хотел бы, чтобы "unmarshall" JSON вернулся в эквивалентный Java-объект. Бонусы, если решение работает как с Spring, так и с java EE. Спасибо!

4b9b3361

Ответ 1

Для разборки сложных графиков объектов с использованием jaxb @XmlID и @XmlIDREF выполняется для.

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

Это приведет к следующему фрагменту json-фрагмента

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

Unmarshalling этого json гарантирует, что detail и detailAgain являются одинаковыми экземплярами.

Две аннотации являются частью jaxb, поэтому она будет работать в Spring, а также в java EE. Маршаллинг для json не является частью стандарта, поэтому я использую moxy в примере.

Обновление

Явно использование moxy не обязательно в ресурсе JAX-RS. Следующие ярлыки отлично работают на контейнере java-EE-7 (glassfish 4.1.1) и приводят к вышеупомянутому фрагменту json:

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

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

Ответ 2

После многих проблем я объясняю Кассио Маццочи Молину, что "использование сущностей в вашем REST API не может быть хорошей идеей"

Я бы сделал, чтобы бизнес-уровень преобразовывал объекты сущности в DTO.

Вы можете сделать это очень легко с помощью библиотек, таких как mapstruct

Если вы все еще хотите продолжить эту плохую практику, вы можете использовать jackson и настроить ваш джектор-картограф

Ответ 3

У меня была такая же проблема, и в результате я использовал аннотации Jackson для своих сущностей для управления сериализацией:

Что вам нужно, это @JsonIdentityReference (alwaysAsId = true), чтобы проинструктировать сериализатор bean, что эта ссылка должна быть только идентификатором. Вы можете увидеть пример моего репо:

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; 

Если вы хотите полностью контролировать, как ваши объекты представлены как JSON, вы можете использовать JsonView для определения того, какое поле сериализовано для вашего представления.

@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

Приветствия!

Ответ 4

для этой проблемы Существует два решения. 1 - использование вида Jackson Json 2- Создание двух классов сопоставления для внутренней структуры. один из них включает пользовательские поля, а другой включает все поля...

Я думаю, что взгляд Jackson Json - лучшее решение...

Ответ 5

Пройдите через библиотеку FLEXJSON, чтобы включить/исключить иерархию вложенных классов при сериализации объектов Java.

Примеры для flexjson.JSONSerializer представлены здесь

Ответ 6

Вы можете отсоединить объект JPA до сериализации, если вы используете lazyloading, чтобы не загружать вспомогательные объекты.

Другой способ, но зависит от API-интерфейса последовательного интерфейса JSON, вы можете использовать аннотацию "переходный" или специфический.

Почему JPA имеет аннотацию @Transient?

Плохой способ - использовать инструмент, например dozer, для копирования объекта JPA в другом классе, только для свойств, необходимых для json (но он работает... немного накладных расходов на память, процессор и время...)

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