nuutrino
nuutrino

Reputation: 141

Jackson custom serializer: change object to be a simple property and value in containing object

The problem I am having is that I have a common object which is used in many places, in this example the common object is Student. Student is a property of another object called Enrolment (however it could be a property of many many other classes). Now without custom serialization being applied to Student I will get something like this:

{"session":"9am Wednesday","student":{"firstName":"Joe","lastName":"Bloggs"}}

What I would like to do is to apply a custom serializer to Student so that anywhere it appears in my code, in this example for instance it is inside the Enrolment class, I will get this:

{"session":"9am Wednesday","firstName":"Joe","lastName":"Bloggs"}

Or if I so choose this:

{"session":"9am Wednesday","first":"Joe","last":"Bloggs"}

Or maybe even this:

{"session":"9am Wednesday","name":"Joe Bloggs"}

Here is my example code:

public class Enrolment {

    private String session;
    private Student student;

    public Enrolment(String session, Student student) {
        this.session = session;
        this.student = student;
    }

    public String getSession() {
        return session;
    }

    public void setSession(String session) {
        this.session = session;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
}

@JsonSerialize(using = StudentSerializer.class)
public class Student {

    private String firstName;
    private String lastName;

    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

public class StudentSerializer extends JsonSerializer<Student> {

    @Override
    public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        // ??????
    }
}

public class Launcher {

  public static void main(String[] args) throws Exception{

      ObjectMapper mapper = new ObjectMapper();
      Student student = new Student("Joe", "Bloggs");
      Enrolment enrolment = new Enrolment("9am Wednesday", student);

      System.out.println(mapper.writeValueAsString(enrolment));

  }
}

It would be a massive bonus if you could provide a deserializer too so that it could accept one of the desired json serialized examples and create the Student object from it. Again the deserializer just like the serializer has to be attached to the Student object so that anywhere it appears it will perform the same action.

Thank you :)

Upvotes: 0

Views: 1345

Answers (2)

nuutrino
nuutrino

Reputation: 141

Here is another option I have stumbled upon:

public class Enrolment {

    private String session;
    private Student student;

    public Enrolment(String session, Student student) {
        this.session = session;
        this.student = student;
    }

    public String getSession() {
        return session;
    }

    public void setSession(String session) {
        this.session = session;
    }

    @JsonUnwrapped
    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
}

public class StudentUnwrappingBeanSerializer extends UnwrappingBeanSerializer {

    public StudentUnwrappingBeanSerializer(BeanSerializerBase src, NameTransformer transformer) {
        super(src, transformer);
    }

    @Override
    public JsonSerializer<Object> unwrappingSerializer(NameTransformer transformer) {
        return new StudentUnwrappingBeanSerializer(this, transformer);
    }

    @Override
    protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {

        Student student = (Student) bean;
        jgen.writeStringField("first", student.getFirstName());
        jgen.writeStringField("last", student.getLastName());
    }

    @Override
    public boolean isUnwrappingSerializer() {
        return true;
    }
}

public class Launcher {

  public static void main(String[] args) throws Exception{

      ObjectMapper mapper = new ObjectMapper();

      mapper.registerModule(new Module() {
          @Override
          public String getModuleName() {
              return "my.module";
          }

          @Override
          public Version version() {
              return Version.unknownVersion();
          }

          @Override
          public void setupModule(SetupContext context) {

              context.addBeanSerializerModifier(new BeanSerializerModifier() {
                  @Override
                  public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
                      if(beanDesc.getBeanClass().equals(Student.class)) {
                          return new StudentUnwrappingBeanSerializer((BeanSerializerBase) serializer, NameTransformer.NOP);
                      }
                      return serializer;
                  }
              });

          }
      });

      Student student = new Student("Joe", "Bloggs");
      ExtendableOption<StudyType> studyType = new ExtendableOption<>(StudyType.DISTANCE);
      Enrolment enrolment = new Enrolment("9am Wednesday", student, studyType);

      System.out.println(mapper.writeValueAsString(enrolment));

  }
}

Upvotes: 0

m4ktub
m4ktub

Reputation: 3121

You want the @JsonUnwrapped annotation available from version 1.9 onwards. You can check the documentation for version 2.0 here, but basically you would have:

public class Enrolment {
    ...
    @JsonUnwrapped
    public Student getStudent() {
        return student;
    }
    ...
}

And you would get your first option:

{"session":"9am Wednesday","firstName":"Joe","lastName":"Bloggs"}

The annotation relies on the default serializer. On the one hand, you cannot have a custom serializer for Student but, on the other hand, you can use other annotations like @JsonProperty and customize Student to have your second option.

Your third option would be done best by also adding a custom getter and setter in Student. In this case you would use @JsonIgnore to avoid serializing the other properties.

Upvotes: 1

Related Questions