Pawan
Pawan

Reputation: 1233

Jackson - Serialize only Ids of objects in a list attribute of a Java Pojo

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("[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);
    }

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" : "[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
  } ]
}

What I want it to print:

{
  "customerId" : 1234,
  "name" : "Pawan",
  "taxId" : "MQZ11DPS",
  "phone" : "+918989898989",
  "address" : "Mumbai, India",
  "emailId" : "[email protected]",
  "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?

Upvotes: 2

Views: 2336

Answers (4)

Pawan
Pawan

Reputation: 1233

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.

Upvotes: 3

Darshan Mehta
Darshan Mehta

Reputation: 30819

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 {
  ..
}

Upvotes: 0

mle
mle

Reputation: 2542

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

Upvotes: 0

Ajmal Muhammad
Ajmal Muhammad

Reputation: 695

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

Or you can use transient keyword for avoiding serialization

Upvotes: 0

Related Questions