Skip to content

Instantly share code, notes, and snippets.

@sscovil
Created February 3, 2014 17:31
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sscovil/8788339 to your computer and use it in GitHub Desktop.
Save sscovil/8788339 to your computer and use it in GitHub Desktop.
This is the correct implementation of @JsonTypeInfo and related annotations when performing polymorphic JSON deserialization on an embedded interface property. It also illustrates the use of an Enum as a type name. See StackOverflow question: http://stackoverflow.com/questions/21485923/java-jackson-polymorphic-json-deserialization-of-an-object-w…
public class Asset {
private AssetType type;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = ImageAssetProperties.class, name = "image"),
@JsonSubTypes.Type(value = DocumentAssetProperties.class, name = "document")
})
private AssetProperties properties;
public String getType() {
return type.toString();
}
@JsonProperty("type")
public void setType(String type) {
this.type = AssetType.fromString(type);
}
@JsonIgnore
public void setType(AssetType type) {
this.type = type;
}
public AssetProperties getProperties() {
return properties;
}
public void setProperties(AssetProperties properties) {
this.properties = properties;
}
}
public interface AssetProperties {
String getSource();
AssetProperties setSource(String source);
String getProxy();
AssetProperties setProxy(String proxy);
}
public enum AssetType {
IMAGE("image"),
DOCUMENT("document");
private String string;
AssetType(String string) {
this.string = string;
}
public static AssetType fromString(String string) {
// Enum.valueOf() method is case sensitive; this method is not.
if (string != null)
for (AssetType assetType : AssetType.values())
if (string.equalsIgnoreCase(assetType.toString()))
return assetType;
throw new IllegalArgumentException(String.format("%s is not a valid AssetType.", string);
}
public String toString() {
return this.string;
}
}
public class DocumentAssetProperties implements AssetProperties {
private String source;
private String proxy;
public String getSource() {
return source;
}
public DocumentAssetProperties setSource(String source) {
this.source = source;
return this;
}
public String getProxy() {
return proxy;
}
public DocumentAssetProperties setProxy(String proxy) {
this.proxy = proxy;
return this;
}
}
public class ImageAssetProperties implements AssetProperties {
private String source;
private String proxy;
private Integer height;
private Integer width;
public String getSource() {
return source;
}
public ImageAssetProperties setSource(String source) {
this.source = source;
return this;
}
public String getProxy() {
return proxy;
}
public ImageAssetProperties setProxy(String proxy) {
this.proxy = proxy;
return this;
}
public Integer getHeight() {
return height;
}
public ImageAssetProperties setHeight(Integer height) {
this.height = height;
return this;
}
public Integer getWidth() {
return width;
}
public ImageAssetProperties setWidth(Integer width) {
this.width = width;
return this;
}
}
public class PolymorphicDeserializationTests {
@Test
public void deserializeJsonSucceeds() {
Asset asset = deserializeJson("{ \"type\": \"document\", \"properties\": { \"source\": \"foo\", \"proxy\": \"bar\" } }");
Assert.assertTrue(asset.getProperties() instanceof DocumentAssetProperties);
}
public Asset deserializeJson(String json) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(json, Asset.class);
}
catch(IOException e) {
Assert.fail("Could not deserialize JSON.", e);
}
return null;
}
}
@mutovkin
Copy link

I've tried to use this approach and unfortunately this does not work for me. So now instead of having 1 instance there are two. One precedes the object and after that follows it. When I'm trying to de-serialize the first object causes a problem like:
"Missing property 'name' for external type id"

@robotdan
Copy link

I also had some difficulty getting this to work. I was receiving the following exception

com.fasterxml.jackson.databind.JsonMappingException: Missing property 'content' 
  for external type id 'contentType

I ended up having to add the @JsonTypeId annotation to the type field referenced as the EXTERNAL_PROPERTY

public interface Content {
}

public class Post implements Content {
  public String content = "bar";
}

public class Foo {

  @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
                include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
                property = "contentType")
  @JsonSubTypes({
    @JsonSubTypes.Type(value = Post.class, name = "post")
  })
  public Content content;

  @JsonTypeId
  public ContentType contentType;

  public enum ContentType {
    "post"
  }
}

I believe this Stack Exchange question is a similar issue.

http://stackoverflow.com/questions/28089484/deserialization-with-jsonsubtypes-for-no-value-missing-property-error/30473550#30473550

@lionelbarrow
Copy link

Any idea how you'd do this without using an external property?

I've got a setup that looks like this:

1st request

{
  "foo": "bar",
  "a": {
     "spam": "lots of spam",
  }
}

2nd request

{
  "foo": "bar",
  "b": {
     "eggs": "lots of eggs",
  }
}
public interface AB { }

@JsonTypeName("b")
public class A implements AB {
  @JsonProperty("spam")
  public String spam:
}

@JsonTypeName("b")
public class B implements AB {
  @JsonProperty("eggs")
  public String eggs;
}

public class Container {
  @JsonProperty("foo")
  public String foo;

  @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
  @JsonSubTypes({@JsonSubTypes.Type(value = A.class), @JsonSubTypes.Type(value = B.class)})
  public AB ab;
}

However my tests trying to deserialize a container end with Unrecognized field "a", not marked as ignorable (2 known properties: "foo", "ab") ... :\

@ranjithmolgu
Copy link

I'm trying serialize java object to JSON by skipping the interface class name in the serialized string. Is there a way we can do it?

The details are at: https://stackoverflow.com/questions/45270140/how-to-serialize-java-to-json-without-including-the-interface-name-in-the-serial

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment