3

In a Spring Boot/Spring Data Rest project i have issues to use a custom JsonSerializer<Set<Object>> on a @OneToMany property. When i do an HTTP GET /collection request i have the following error:

Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Can not override serializer (through reference chain: org.springframework.hateoas.Resources["_embedded"]->java.util.UnmodifiableMap["analogParameters"]->java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not override serializer (through reference chain: org.springframework.hateoas.Resources["_embedded"]->java.util.UnmodifiableMap["analogParameters"]->java.util.ArrayList[0])

Below is an extract of my entity class:

@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="output_parameter_id")
@JsonSerialize(using=InputParametersSerializer.class)
//@Transcient
private Set<InputParameter> inputParameters = new HashSet<InputParameter>();

public Set<InputParameter> getInputParameters() {
    return inputParameters;
}

public void setInputParameters(Set<InputParameter> inputParameters) {
    this.inputParameters = inputParameters;
}

And the JsonSerializer<Set<InputParameter>>

public class InputParametersSerializer 
    extends JsonSerializer<Set<InputParameter>> {

    static final long serialVersionUID = 123L;

    public void serialize (Set<InputParameter> ips, JsonGenerator jg, 
            SerializerProvider sp) 
        throws IOException {

        jg.writeString("Yeah");

    }

}

If i remove @OneToMany and define the property as @transient it works as expected.

InputParameter entity has no Repository associated (it is not exported as a rest resource).

How can a make use of a JsonSerializer on a @OneToMany property?

sgt-hartman
  • 634
  • 6
  • 25
  • Does your `InputParameter` refer to the entity extract you have posted?? – Raghav Nov 08 '16 at 15:45
  • No its another entity called "Parameter" (names are similar but they are differents entities) – sgt-hartman Nov 08 '16 at 15:49
  • 1
    IMHO it's better to separate concerns, i.e. having **DTO's** (Exposed via RestController, with json-related annotations) separated from **entities** (Object mapped to database using JPA or Hibernate). See [this nice post](https://softwareengineering.stackexchange.com/questions/373284/what-is-the-use-of-dto-instead-of-entity). – Benoit Nov 23 '18 at 08:51

1 Answers1

0

I ran into a very similar issue while using Spring Boot 2.1.0. Adding a custom serializer, both with using and keyUsing, works fine, but a custom deserializer with a @OneToMany annotated field throws out the same JsonMappingException: Can not override serializer message you got, while with an @ElementCollection it just plain gets ignored. I suspect Spring Data Rest does some undocumented magic in order to take care of the (de)serialization of these kinds of fields that does not play nice with the addition of a custom deserializer. My workaround to this was adding an extra JSON field through an annotated getter and setter. With your example, it would look like:

@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="output_parameter_id")
private Set<InputParameter> inputParameters = new HashSet<InputParameter>();

public Set<InputParameter> getInputParameters() {
    return inputParameters;
}

public void setInputParameters(Set<InputParameter> inputParameters) {
    this.inputParameters = inputParameters;
}


@JsonSerialize(using=InputParametersSerializer.class)
public Set<InputParameter> getInputParametersSet() {
    return getInputParameters();
}


@JsonDeserialize(using=InputParametersDeserializer.class)
public void setInputParametersSet(Set<InputParameter> inputParameters) {
    setInputParameters(inputParameters);
}

Which will output something like

{
...
  "inputParameters" : ...,
  "inputParametersSet" : ...,
...
}

While not ideal, serialization and deserialization of this field works as expected. alternatively, in order to keep the field name, a similar workaround worked with @ElementCollection but not with @OneToMany:

@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="output_parameter_id")
@JsonIgnore
private Set<InputParameter> inputParameters = new HashSet<InputParameter>();

public Set<InputParameter> getInputParameters() {
    return inputParameters;
}

public void setInputParameters(Set<InputParameter> inputParameters) {
    this.inputParameters = inputParameters;
}


@JsonProperty("inputParameters")
@JsonSerialize(using=InputParametersSerializer.class)
public Set<InputParameter> getInputParametersSet() {
    return getInputParameters();
}

@JsonProperty("inputParameters")
@JsonDeserialize(using=InputParametersDeserializer.class)
public void setInputParametersSet(Set<InputParameter> inputParameters) {
    setInputParameters(inputParameters);
}

In the end I had to go with the first approach.

Sofia Paixão
  • 309
  • 2
  • 16