20

I need to add new property to an object, when serializing to JSON. The value for the property is calculated on runtime and does not exist in the object. Also the same object can be used for creation of different JSON with different set ot fields (kind of having a base class with subclasses, but I don't want to create ones just for JSON generation).

What is the best way of doing that, which doesn't involve creation of custom serializer class, which will take care of serializing of whole set of object's fields? Or may be it is possible to inherit some "basic" serializer, and simply take it's output and add new field to it somehow?

I learned about mixins, and looks like it is possible to rename/hide some fields, however it seems not be possible to add an extra one.

bruno
  • 2,213
  • 1
  • 19
  • 31
jdevelop
  • 12,176
  • 10
  • 56
  • 112
  • Note: Most messaging platforms have the concept of _headers_ whose job is to hold meta-data about the content. Sounds exactly like the usecase described here! – drekbour Jun 18 '21 at 08:33

3 Answers3

16

Can you not just add a method in value class? Note that it does not have to be either public, or use getter naming convention; you could do something like:

public class MyStuff {
   // ... the usual fields, getters and/or setters

   @JsonProperty("sum") // or whatever name you need in JSON
   private int calculateSumForJSON() {
        return 42; // calculate somehow
   }
}

Otherwise you could convert POJO into JSON Tree value:

JsonNode tree = mapper.valueToTree(value);

and then modify it by adding properties etc.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • 1
    This doesn't work if the extra field isn't part of the object being serialized's responsibility. Eg. the URL of an object... It isn't the object's job to know it's own address, it's a higher layer's job. – cdeszaq Apr 30 '14 at 18:37
  • 1
    Quite, but why is it then serialized in same JSON Object? This sounds like potentially bad design for JSON payload. Although granted JSON has the problem that there is no simple division between data, metadata (one can come up with naming conventions) – StaxMan May 02 '14 at 06:10
  • 1
    The internal object representation is not the same as the external resource representation (which may need to change depending on Accepts headers). URL or address at which a resource lives may change, and is not the responsibility of the internal object, but of the routes or some other higher layer. – cdeszaq May 02 '14 at 14:23
  • 1
    I see. So in a way like adding/removing envelope information. – StaxMan May 02 '14 at 23:48
5

2021 calling...

Simplest way I found to do this is @JsonUnwrapped:

public class Envelope<T> {
  @JsonUnwrapped // content's fields are promoted alongside the envelope's
  public T content;
  // Transmission specific fields
  public String url;
  public long timestamp;
}

This works (bi-directionally) so long as Envelope's fieldnames do not clash with those of content. Also has a nice feature of keeping the transmission properties at the end of the serialised JSON.

drekbour
  • 2,895
  • 18
  • 28
  • As of now, this doesn't work if T is a List, Array or Map. – Dalibor Filus Jun 18 '21 at 04:18
  • Fair enough (though I'm a little suprised that Map doesn't work) but the problem has an easy solution in those cases - Decorate or copy the content with the extra fields. – drekbour Jun 18 '21 at 08:28
3

One option is to add a field for this property and set it on the object before writing to JSON. A second option, if the property can be computed from other object properties you could just add a getter for it, for example:

public String getFullName() {
  return getFirstName() + " " + getLastName();
}

And even though there's no matching field Jackson will automatically call this getter while writing the JSON and it will appear as fullName in the JSON output. If that won't work a third option is to convert the object to a map and then manipulate it however you need:

ObjectMapper mapper //.....
MyObject o //.....
long specialValue //.....
Map<String, Object> map = mapper.convertValue(o, new TypeReference<Map<String, Object>>() { });
map.put("specialValue", specialValue);

You're question didn't mention unmarshalling but if you need to do that as well then the first option would work fine but the second two would need some tweaking.

And as for writing different fields of the same object it sounds like a job for @JsonView

HiJon89
  • 891
  • 6
  • 6
  • The "convert to a map" object sounds like it might be OK, but it seems a bit clunky. The "add a field" option doesn't work if the extra field isn't part of the object being serialized's responsibility. Eg. the URL of an object... It isn't the object's job to know it's own address, it's a higher layer's job. – cdeszaq Apr 30 '14 at 18:37