Джексон - сериализовать только идентификаторы объектов в атрибуте списка Java Pojo

Я хочу оптимизировать данные json для отправки по проводам. У меня есть три модели в моем коде. Это клиент, счет-фактура и частности.

Класс "Клиент"

@Data
public class Customer implements Serializable {

    private long customerId;

    private String name;

    private String taxId;

    private String phone;

    private String address;

    private String emailId;

    private Date created;

    private List<Invoice> invoices;
}

Класс Invoice

@Data
public class Invoice implements Serializable {

    private String invoiceId;

    private List<Particular> particulars;

    private Date invoiceDate;
}

Особый класс

@Data
public class Particular {
    private String item;
    private int quantity;
    private float tax;
    private int unitPrice;
}

Мой тестовый код:

@Test
    public void makeCustomerJsonWithInvoices() throws JsonProcessingException {
        Customer customer = new Customer();
        customer.setCustomerId(1234);
        customer.setName("Pawan");
        customer.setPhone("+918989898989");
        customer.setEmailId("[email protected]");
        customer.setAddress("Mumbai, India");
        customer.setTaxId("MQZ11DPS");
        customer.setCreated(new Date());

        Invoice invoice1 = new Invoice();
        invoice1.setInvoiceId("A-1");
        Particular particular1 = new Particular("abc", 1, 0, 12);
        Particular particular2 = new Particular("xyz", 2, 0, 20);
        invoice1.setInvoiceDate(new Date());
        invoice1.setParticulars(Arrays.asList(particular1, particular2));

        Particular particular3 = new Particular("mno", 2, 0, 15);
        Invoice invoice2 = new Invoice();
        invoice2.setInvoiceId("A-2");
        invoice2.setParticulars(Arrays.asList(particular3));
        invoice2.setInvoiceDate(new Date());
        customer.setInvoices(Arrays.asList(invoice1, invoice2));

        String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
        System.out.println(value);
    }

Здесь я хочу избежать избыточности, сериализовав счет-фактуру, чтобы полученный json был компактным. Это должно быть достигнуто путем отправки только значения атрибута invoiceId вместо всего объекта Invoice json.

Что печатает тестовый код:

{
  "customerId" : 1234,
  "name" : "Pawan",
  "taxId" : "MQZ11DPS",
  "phone" : "+918989898989",
  "address" : "Mumbai, India",
  "emailId" : "[email protected]",
  "created" : 1553243962038,
  "invoices" : [ {
    "invoiceId" : "A-1",
    "particulars" : [ {
      "item" : "abc",
      "quantity" : 1,
      "tax" : 0.0,
      "unitPrice" : 12
    }, {
      "item" : "xyz",
      "quantity" : 2,
      "tax" : 0.0,
      "unitPrice" : 20
    } ],
    "invoiceDate" : 1553243962038
  }, {
    "invoiceId" : "A-2",
    "particulars" : [ {
      "item" : "mno",
      "quantity" : 2,
      "tax" : 0.0,
      "unitPrice" : 15
    } ],
    "invoiceDate" : 1553243962039
  } ]
}

Что я хочу напечатать:

{
  "customerId" : 1234,
  "name" : "Pawan",
  "taxId" : "MQZ11DPS",
  "phone" : "+918989898989",
  "address" : "Mumbai, India",
  "emailId" : "[email protected]",
  "created" : 1553243962038,
  "invoices" : [ {
    "invoiceId" : "A-1"
  }, {
    "invoiceId" : "A-2"
  } ]
}

@Data – это аннотация lombok, используемая для создания геттеров и сеттеров.

Я попытался добавить аннотацию @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "invoiceId") к классу Invoice, но это не меняет вывод.

Обратите внимание, что я хочу, чтобы эта сериализация с Invoice происходила только тогда, когда он передается в качестве дочернего элемента модели контейнера. Если я хочу отправить счет-фактуру самостоятельно, он должен сериализовать все поля в модели счета-фактуры. Я считаю, что это распространенный сценарий при внедрении RESTful WS.

Нужно ли для этого писать сериализатор клиента?


person Pawan    schedule 22.03.2019    source источник
comment
Вы можете использовать @JsonIgnore для игнорирования свойств   -  person Ajmal Muhammad    schedule 22.03.2019
comment
На самом деле это просто минимальный пример. Возможно, мне придется аннотировать @JsonIgnore во многих полях, если структура класса большая.   -  person Pawan    schedule 22.03.2019


Ответы (4)


Я могу добиться этого, изменив класс Customer следующим образом.

@Data
public class Customer implements Serializable {

    private long customerId;

    private String name;

    private String taxId;

    private String phone;

    private String address;

    private String emailId;

    private Date created;

    @JsonIdentityInfo(generator= ObjectIdGenerators.PropertyGenerator.class, property="invoiceId")
    @JsonIdentityReference(alwaysAsId=true)
    private List<Invoice> invoices;
}

Ответ взят из https://stackoverflow.com/a/17583175/1365340.

С этим я могу сгенерировать Customer json со списком идентификаторов счетов. Объект Invoice при сериализации отдельно получает все значения из всех своих полей в json.

person Pawan    schedule 22.03.2019

Вы можете использовать @JsonIgnore для игнорирования свойств в ответе JSON.

Или вы можете использовать ключевое слово transient, чтобы избежать сериализации.

person Ajmal Muhammad    schedule 22.03.2019
comment
Помните о возможных конфликтах с transient интерпретацией других фреймворков, таких как заполнение поля в Spring Data Mongo. - person mle; 22.03.2019

Рассмотрим следующий компонент как небольшую модификацию вашего случая:

@Data
@JsonFilter("idOnlyFilter")
@AllArgsConstructor
class Complex {
    private String id;
    private List<String> aList;
    private Date aDate;
}

Вы можете использовать концепцию @JsonFilter, чтобы определить действительно гранулярно, для каждого компонента, который вы хотите, каковы условия для сериализации. Обратите особое внимание на имя фильтра idOnlyFilter в конфигурации ObjectMapper, а также в аннотации @JsonFilter.

Это работает, как показано ниже:

@Test
public void includeOnlyOneField() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    FilterProvider filters = new SimpleFilterProvider()
            .addFilter("idOnlyFilter", SimpleBeanPropertyFilter.filterOutAllExcept("id"));

    Complex complex = new Complex("a string", List.of(), new Date());

    // when
    String complexAsString = mapper.writer(filters).writeValueAsString(complex);

    // then
    assertThat(complexAsString).isEqualTo("{\"id\":\"a string\"}");
}
person mle    schedule 22.03.2019
comment
Это довольно обобщенно. Я не могу предположить, что все классы, я хочу, чтобы их идентификатор был сериализован, чтобы иметь свойство с именем id. Также я могу не захотеть применять это правило к некоторым классам. В нашем коде есть много мест, где сериализация происходит внутри, и у нас нет доступа к ObjectMapper. - person Pawan; 22.03.2019

Вы можете использовать @JsonAutoDetect в классе Invoice для сериализации только поля invoiceId, например:

@JsonAutoDetect(
    fieldVisibility = Visibility.NONE,
    setterVisibility = Visibility.NONE,
    getterVisibility = Visibility.NONE,
    isGetterVisibility = Visibility.NONE,
    creatorVisibility = Visibility.NONE
)
@Data
public class Invoice implements Serializable {

    @JsonProperty (access = READ_ONLY)
    private String invoiceId;

    private List<Particular> particulars;

    private Date invoiceDate;
}

Это гарантирует, что только invoiceId будет проходить по сети, посмотрите документацию здесь.

Обновить

Если вы хотите иметь такое поведение только тогда, когда Invoice отправляется как вложенный объект, вы можете установить для других полей значение null (или не устанавливать эти поля в первую очередь) и использовать аннотацию @JsonInclude, например:

@JsonInclude(Include.NON_NULL)
@Data
public class Invoice implements Serializable {
  ..
}
person Darshan Mehta    schedule 22.03.2019
comment
При таком подходе я не могу получить другие поля, когда хочу сериализовать всю модель (с каждым полем). Обратите внимание, что я хочу оптимизировать Invoice json только тогда, когда он отправляется в виде списка внутри модели контейнера (Customer в нашем случае). - person Pawan; 22.03.2019
comment
это грязно и может быть опасно. Итак, вы хотите иметь код, который явно устанавливает поля без идентификатора в нуль. Это может вызвать некоторые побочные эффекты, если доступ к отправляемой ссылке на счет-фактуру осуществляется в других местах. - person Pawan; 22.03.2019
comment
У меня был бы код, который не set эти поля в первую очередь, если они не нужны для распространения по сети. - person Darshan Mehta; 22.03.2019