user372743
user372743

Reputation:

Duplicate JSON Field with Jackson

I am using Jackson for JSON (de)serialization in conjunction with Spring. However I am having an issue with a field being twice in some cases.

I have an abstract class:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "mimeType")
@JsonSubTypes({
    @JsonSubTypes.Type(value = ImageBookmarkJsonModel.class, name = "image/jpeg"),
    @JsonSubTypes.Type(value = EpubBookmarkJsonModel.class, name = "application/epub+zip")
})
public abstract class AbstractBookmarkJsonModel extends AbstractJsonModel {
    protected String mimeType;
    // Removed other fields for brevity

    public String getMimeType() {
        return mimeType;
    }

    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }

    @Override
    public String toString() {
        ObjectMapper mapper = new ObjectMapper();

        try {
            return mapper.writeValueAsString(this);
        } catch (IOException e) {
            throw new IllegalStateException("Cannot convert object of type " + this.getClass().toString() + " to JSON", e);
        }
    }
}

And a concrete class extend the abstract:

public class EpubBookmarkJsonModel extends AbstractBookmarkJsonModel {
    private static final long serialVersionUID = 1L;
    // Removed other fields for brevity

    public EpubBookmarkJsonModel() {
        this.mimeType = "application/epub+zip";
    }
}

The problem is that when I serialize this JSON, I get a duplicate mimeType field:

{
  "mimeType": "application/epub+zip",
  "mimeType": "application/epub+zip",
  "userId": 24,
  "acid": "ACID-000000000029087",
  "added": "2013-08-14T12:02:17Z",
  "epubBookmarkId": 34,
  "cfi": "epubcfi(/6/4!/2/68)",
  "context": "CONTEXT"
}

I have tried using the recommendation of previous answers to use the @JsonAutoDetect annotation to specify that only fields on a class should be used as well as setting the same on the ObjectMapper, however this does not fix the problem.

Annotation:

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
        setterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE,
        isGetterVisibility = JsonAutoDetect.Visibility.NONE)

ObjectMapper:

    ObjectMapper mapper = new ObjectMapper();
    mapper.getSerializationConfig().getDefaultVisibilityChecker()
            .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
            .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
            .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
            .withCreatorVisibility(JsonAutoDetect.Visibility.NONE);

Upvotes: 59

Views: 34907

Answers (6)

Thomas Vaughan
Thomas Vaughan

Reputation: 954

I solved this by using the JsonTypeInfo.As.EXISTING_PROPERTY in the @JsonTypeInfo annotation.

Upvotes: 78

Chris
Chris

Reputation: 23

For me, duplicate fields in JSON occurred when I accidently had same name for field and JSON property like .

@JsonProperty("Feild1") private String Field1;

I fixed it like this

@JsonProperty("Feild1") private String field1;

Upvotes: 0

bmel
bmel

Reputation: 41

Had the same problem. We're using Lombok, and I got this work with @JsonProperty access:

  @Getter
  @Setter
  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // Prevents duplication when serializing to JSON (subtype discriminator property)
  private RuleType ruleType;

I like this solution, since:

  • No dependency on another lib
  • Did not have to change my data model
  • Works with Lombok (less clutter)

And yes, need visible=true so that the property will get populated. In our case, it's an Enum with some behavior dispatching, so we certainly need it for our purposes, and it's great to be able to use it for Json subtype discrimination as well.

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  property = "ruleType",
  visible = true)

Upvotes: 2

Fedulov Oleg
Fedulov Oleg

Reputation: 87

This case probably just with legacy versions of jacson lib and it's simply resolved by moving yours @JsonProperty(value = "Your_CUSTOM_Name") from fields to getters.

Upvotes: 2

ZacharyP
ZacharyP

Reputation: 740

I was having this exact same problem with the duplicate output. I found a solution that did not involve another property, and allowed me to not remove the original property. First, I set the visible flag to true for JsonTypeInfo. Then, I added a JsonIgnore annotation to the property declaration and the getter (but not the setter). This is so far outputting the JSON correctly with only one key for the type property.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "mimeType")
@JsonSubTypes({
    @JsonSubTypes.Type(value = ImageBookmarkJsonModel.class, name = "image/jpeg"),
    @JsonSubTypes.Type(value = EpubBookmarkJsonModel.class, name = "application/epub+zip")
})
public abstract class AbstractBookmarkJsonModel extends AbstractJsonModel {
    @JsonIgnore
    @JsonProperty("mimeType")
    protected String mimeType;

    @JsonIgnore
    @JsonProperty("mimeType")
    public String getMimeType() {
        return mimeType;
    }

    @JsonProperty("mimeType")
    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }

}

To note, this is with fasterxml jackson jackson-databind 2.1.1

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.1.1</version>
</dependency>

Upvotes: 13

Katona
Katona

Reputation: 4901

This behaviour is caused by the annotations placed on class AbstractBookmarkJsonModel:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "mimeType")
@JsonSubTypes({
    @JsonSubTypes.Type(value = ImageBookmarkJsonModel.class, name = "image/jpeg"),
    @JsonSubTypes.Type(value = EpubBookmarkJsonModel.class, name = "application/epub+zip")
})

@JsonTypeInfo tells Jackson to serialize the logical type name (JsonTypeInfo.Id.NAME) as a property (JsonTypeInfo.As.PROPERTY) with name mimeType (property = "mimeType"). With @JsonSubTypes.Type you assign the logical name application/epub+zip to EpubBookmarkJsonModel.

When it comes to serialization, Jackson serializes the logical name as a property mimeType = "application/epub+zip" then the properties of the object among them mimeType which happens to have the same value as the logical name application/epub+zip (assigned in the constructor).

I think mimeType should be changed to objectType in the @JsonTypeInfo annotation or even better to remove the mimeType field since Jackson will take care of that through type info serialization.

Upvotes: 9

Related Questions