0

I have two classes Athlete and Injury, the last one contains Athlete object, when the serialization happens I get the following JSON representation back: {"id":X,"kindOfInjury":"...","muscle":"...","side":"...","outOfTrainig":Y,"injuryDate":"2018-Jun-02","athlete":{"id":X,"firstName":"...","lastName":"...","age":X,"email":"..."}}

I don't want to get all the information about Athlete - just an id value, like "athleteId":1, instead of getting the entire object representation.

So, I have found that I need to apply my custom Serializer which implements StdSerializer on Injury class. So this is what I got so far:

class InjurySerializer extends StdSerializer<Injury> {

    public InjurySerializer() {
        this(null);
    }

    public InjurySerializer(Class<Injury> i) {
        super(i);
    }

    @Override
    public void serialize(
            Injury value, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonProcessingException {

        jgen.writeStartObject();
        jgen.writeNumberField("id", value.getId());
        jgen.writeStringField("kindOfInjury", value.getKindOfInjury());
        jgen.writeStringField("muscle", value.getMuscle());
        jgen.writeStringField("side", value.getSide());
        jgen.writeNumberField("outOfTraining", value.getOutOfTraining());
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MMM-dd");
        Date date = new Date();
        String ourformat = formatter.format(date.getTime());
        jgen.writeStringField("injuryDate", ourformat);
        jgen.writeNumberField("athleteId", value.getAthlete().getId());
        jgen.writeEndObject();
    }
}

And the actual Injury class:

@Entity
@Table(name = "INJURY")
@JsonSerialize(using = InjurySerializer.class)
public class Injury {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "INJURY_ID")
    private Long id;

    @Column(name = "KIND_OF_INJURY")
    private String kindOfInjury;

    @Column(name = "MUSCLE")
    private String muscle;

    @Column(name = "SIDE")
    private String side;

    @Column(name = "OUT_OF_TRAINING")
    private Integer outOfTraining;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MMM-dd")
    @Column(name = "INJURY_DATE")
    private Date injuryDate;

    @ManyToOne
    @JoinColumn(name = "ATHLETE_ID")
    private Athlete athlete;

So, this solution works, but it looks terrible...

Question is the following: 1) Is there any mechanism which provides me functionality to change the serialization of only ONE property which I really need, instead of writing all this tedious code, where the actual change is only in this line? :

jgen.writeNumberField("athleteId", value.getAthlete().getId());

2) Could you recommend me something to read about Jackson because at this point I have a little bit mess in my head about it?

Thanks for the patience and I'm looking forwards for your responses :)

Maksim
  • 351
  • 1
  • 2
  • 12
  • You can use the property "JsonIgnore", here's more information about: [Jackson DOCs](https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonIgnore.html). I recommend you to follow baeldung tutorials, here's some useful links: 1. https://www.baeldung.com/jackson 2. https://www.baeldung.com/jackson-ignore-properties-on-serialization – Ricardo Brasil Jun 21 '19 at 22:35
  • Yeah, I have checked it before, but the problem is, if I mark Athlete field as @JsonIgnore in the Injury class, then I can't use this field anymore, so the serialization of the entire athlete object in the Injury class will be blocked -> this is not what I want, I want that from Athlete property there will be only one attribute serialized -> just id field. – Maksim Jun 21 '19 at 22:53
  • Maybe this help https://stackoverflow.com/questions/25893985/jackson-how-to-serialize-only-annotated-properties – Ricardo Brasil Jun 21 '19 at 23:24

3 Answers3

1

You might find it less tedious to use the @JsonIgnore annotation instead of writing a custom serializer. Take this example

public class Person {

  private int id;

  @JsonIgnore
  private String first;

  @JsonIgnore
  private String last;

  @JsonIgnore
  private int age;

  // getters and setters omitted
}

When Jackson serializes this class, it only includes the "id" property in the resulting JSON.

  @Test
  void serialize_only_includes_id() throws JsonProcessingException {
    final var person = new Person();
    person.setId(1);
    person.setFirst("John");
    person.setLast("Smith");
    person.setAge(22);

    final var mapper = new ObjectMapper();
    final var json = mapper.writeValueAsString(person);
    assertEquals("{\"id\":1}", json);
  }
jdgilday
  • 866
  • 7
  • 21
  • If I annotate Athlete class in the following style, I will loose all the fields required for athlete entity. I need full representation of Athlete class when I use "/athletes/1" URL and only id value when I use "athletes/1/injuries" URL. – Maksim Jun 22 '19 at 20:13
  • I see. In that case, I concur that @kimreik's answer https://stackoverflow.com/a/56713883 is your best option: use a different data model. – jdgilday Jun 27 '19 at 01:14
1

You can use the Data Transfer Object (DTO) for that purposes.

Create a simple POJO like this:

public class InjuryDTO {

  //all other required fields from Injury model...

  @JsonProperty("athlete_id")
  private Long athleteId;
}

And converter for it:

@Component
public class InjuryToDTOConverter{

  public InjuryDTO convert(Injury source){
    InjuryDTO target = new InjuryDTO();
    BeanUtils.copyProperties(source, target); //it will copy fields with the same names
    target.setAthleteId(source.getAthlete().getId());
    return target;
  }
}

You can use it like that:

@RestController("/injuries")
public class InjuryController {

  @Autowired
  private InjuryToDTOConverter converter;

  @Autowired
  private InjuryService injuryService;

  @GetMapping
  public InjuryDTO getInjury(){
    Injury injury = injuryService.getInjury();
    return converter.convert(injury);
  }
}

The benefit of this approach is that you can have multiple DTOs for different purposes.

kimreik
  • 635
  • 1
  • 7
  • 25
0

You can try manupulating json string using basic string replace method. I ran your json and converted it to your desired format:

public static void main(String args[]) {
    String json = "{\"id\":123,\"kindOfInjury\":\"...\",\"muscle\":\"...\",\"side\":\"...\",\"outOfTrainig\":Y,\"injuryDate\":\"2018-Jun-02\",\"athlete\":{\"id\":456,\"firstName\":\"...\",\"lastName\":\"...\",\"age\":14,\"email\":\"...\"}}";
    JsonObject injury = new JsonParser().parse(json).getAsJsonObject();
    JsonObject athelete = new JsonParser().parse(injury.get("athlete").toString()).getAsJsonObject();
    String updateJson = injury.toString().replace(injury.get("athlete").toString(), athelete.get("id").toString());
    updateJson = updateJson.replace("athlete", "athleteId");
    System.out.println(updateJson);
}

output:

{"id":123,"kindOfInjury":"...","muscle":"...","side":"...","outOfTrainig":"Y","injuryDate":"2018-Jun-02","athleteId":456}

Dependency:

implementation 'com.google.code.gson:gson:2.8.5'

If you can replace with regex that will be bit more cleaner.

Sayantan Mandal
  • 1,246
  • 14
  • 20