2

I want to optimize the json data to be sent on wire. I have three models in my code. These are Customer, Invoice and Particular.

The Customer class

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

The Invoice class

@Data
public class Invoice implements Serializable {

    private String invoiceId;

    private List<Particular> particulars;

    private Date invoiceDate;
}

The Particular class

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

My test code:

@Test
    public void makeCustomerJsonWithInvoices() throws JsonProcessingException {
        Customer customer = new Customer();
        customer.setCustomerId(1234);
        customer.setName("Pawan");
        customer.setPhone("+918989898989");
        customer.setEmailId("something@something.com");
        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);
    }

What I want here is to avoid the redundancy by serializing the Invoice so that the resulting json would be compact. This should be achieved by only sending the invoiceId attribute value instead of whole Invoice object json.

What the test code prints:

{
  "customerId" : 1234,
  "name" : "Pawan",
  "taxId" : "MQZ11DPS",
  "phone" : "+918989898989",
  "address" : "Mumbai, India",
  "emailId" : "something@something.com",
  "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
  } ]
}

What I want it to print:

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

The @Data is lombok annotation used to generate getters and setters.

I tried to add @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "invoiceId") annotation to Invoice class but this doesn't change the output.

Note that I want this serialization with Invoice happen only when it is passed as a child to a container Model. If I want to send Invoice independently, it shall serialize all fields in Invoice model. I believe this is a common scenario while implementing RESTful WS.

Do I need to write customer serializer for this?

Pawan
  • 1,183
  • 16
  • 29

4 Answers4

3

I am able to achieve this by modifying the Customer class in following way.

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

The answer is inspired from https://stackoverflow.com/a/17583175/1365340

With this I can generate Customer json with invoice Id list. The Invoice object when serialized separately gets all values from all its fields in json.

Pawan
  • 1,183
  • 16
  • 29
0

You can use @JsonAutoDetect on Invoice class to serialize only invoiceId field, e.g.:

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

This will make sure only invoiceId goes through the wire, have a look at the documentation here.

Update

If you want to have this behaviour only when Invoice is sent as nested object then you can set the other fields to null (or not set those fields in the first place) and use @JsonInclude annotation, e.g.:

@JsonInclude(Include.NON_NULL)
@Data
public class Invoice implements Serializable {
  ..
}
Darshan Mehta
  • 30,102
  • 11
  • 68
  • 102
  • With this approach, I cannot get the other fields when I want to serialize the whole Model (with every fields). Note that I want to optimize `Invoice` json only when it is sent as a list inside container Model (`Customer` in our case). – Pawan Mar 22 '19 at 10:09
  • this is dirty and may be dangerous. So you want to have a code which explicitly sets non Id fields to null. This may cause some side effects if the Invoice reference to be sent is accessed at some other places. – Pawan Mar 22 '19 at 10:43
  • I would have a code that doesn't `set` these fields in the first place if they are not needed to be propagated to the wire. – Darshan Mehta Mar 22 '19 at 10:51
0

You can use @JsonIgnore for ingnoring properties in JSON response.

Or you can use transient keyword for avoiding serialization

Ajmal Muhammad
  • 685
  • 7
  • 25
  • 2
    Be aware of probable conflicts with the `transient` interpretation of other frameworks such as [field population in Spring Data Mongo](https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping.property-population). – mle Mar 22 '19 at 09:38
0

Consider the following bean as a slight modification of your case:

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

You could use the @JsonFilter concept to define really granular, on every bean you want, what the conditions for serializing are. Pay especially attention to the filter name idOnlyFilter in the ObjectMapper configuration as well as in the @JsonFilter annotation.

This works as shown below:

@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\"}");
}
mle
  • 2,466
  • 1
  • 19
  • 25
  • This is quite generalized. I cannot assume all classes I want their id to be serialized to have property named id. Also I may not want to apply this rule to some classes. There are many places in our code the serialization happens internally and we do not have access to ObjectMapper. – Pawan Mar 22 '19 at 10:24