如何使用Jackson运行时泛型?

时间:2017-06-14 20:04:33

标签: java json generics jackson deserialization

我是杰克逊的新手,并且在使用通用字段反序列化JSON方面存在问题。这是我想用杰克逊解析的JSON。

{
  "topic": {
    "headline": {
      ...
    },
    "body": [
      {
        "type": "complex",
        "content": {
          "player_template": "12345",
          "width": 600,
          "height": 338,
          "url": "http://...",
          "caption": "foobar",
          "vid": "12345",
          "watch_url": "http://...",
          "embed_html": "<script..."
        },
        "preview_image_url": "https://...",
        "position": 0
      },
      {
        "content": "foobar",
        "type": "simple",
        "position": 1
      }
    ],
    "type": "some type",
    "part": "image",
    "box_link": [
      {
        ...
      },
      ...
    ]
  }
}

注意

topic > body > element[0] > contentobject,但topic > body > element[1] > contentstringbody元素可能只包含stringobject或两者。

以下是bodycontent的Java类。

public class Body<T> {

    // @JsonDeserialize(using = ContentDeserializer.class)
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property = "type")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = String.class, name = "simple"),
            @JsonSubTypes.Type(value = Content.class, name = "complex")
    })
    @JsonProperty("content")
    private T mContent;

    @JsonProperty("type")
    private String mType;

    @JsonProperty("preview_image_url")
    private String mPreviewImageUrl;

    @JsonProperty("position")
    private int mPosition;

    // getter and setter
}

public class Content {

    @JsonProperty("player_template")
    private String mPlayerTemplate;

    @JsonProperty("width")
    private int mWidth;

    @JsonProperty("height")
    private int mHeight;

    @JsonProperty("url")
    private String mUrl;

    @JsonProperty("caption")
    private String mCaption;

    @JsonProperty("vid")
    private String mVid;

    @JsonProperty("watch_url")
    private String mWatchUrl;

    @JsonProperty("embed_html")
    private String mEmbedHtml;

    // getter and setter
}

我尝试使用JsonSubTypes注释将JSON映射到POJO,因此如果type等于complex,那么JSON应该映射到Content类,{{1键入映射类应该是simple对象。问题是杰克逊将String内容转换为complex我不想要的内容。对于LinkedHashMap内容没有问题,它会转换为simple,但我认为杰克逊使用内部逻辑来映射这种正确的方式。

如果我尝试使用String注释,则不会调用反序列化方法。就像杰克逊忽视注释并自己创作一样。

我确实犯了错误?如何将JsonDeserialize内容解析为complex POJO?

2 个答案:

答案 0 :(得分:1)

@JsonTypeInfo@JsonSubTypes旨在帮助继承而不是泛型。由于StringContent隐式扩展Object,因此您可以将mContent定义为Object。以下是您的Body课程的样子:

class Body {
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = String.class, name = "simple"),
            @JsonSubTypes.Type(value = Content.class, name = "complex")
    })
    @JsonProperty("content")
    private Object mContent;

指定

include = JsonTypeInfo.As.PROPERTY

杰克逊将在JSON的type字段中查找content。但在您的情况下,type位于JSON中body数组的元素中,与content处于同一级别。在这种情况下,您必须指定

include = JsonTypeInfo.As.EXTERNAL_PROPERTY

所以杰克逊会在JSON的type字段之外寻找content

请注意,如果您拥有像Body<T>这样的通用类,则必须向Jackson提供类型T以进行反序列化(例如使用TypeReference)。如果您想在同一个集合/数组中使用Body<String>Body<Content>,我不会看到它会如何工作。集合的类型必须是List<Body>,这不再是通用的。

答案 1 :(得分:0)

我已使用自定义JsonDeserializer解决了此问题。

@JsonDeserialize(using = BodyDeserializer.class)
public class Body<T> {

    @JsonProperty("content")
    private T mContent;

    @JsonProperty("type")
    private String mType;

    @JsonProperty("preview_image_url")
    private String mPreviewImageUrl;

    @JsonProperty("position")
    private int mPosition;

    // getter and setter
}

public class BodyDeserializer extends StdDeserializer<Body> {

    private static final String CAPTION = "caption";
    private static final String CONTENT = "content";
    private static final String COMPLEX = "complex";
    private static final String EMBED_HTML = "embed_html";
    private static final String HEIGHT = "height";
    private static final String PLAYER_TEMPLATE = "player_template";
    private static final String POSITION = "position";
    private static final String PREVIEW_IMAGE_URL = "preview_image_url";
    private static final String PROVIDER = "provider";
    private static final String TYPE = "type";
    private static final String URL = "url";
    private static final String VID = "vid";
    private static final String WATCH_URL = "watch_url";
    private static final String WIDTH = "width";

    public BodyDeserializer() {
        this(Body.class);
    }

    protected BodyDeserializer(Class<Body> vc) {
        super(vc);
    }

    @Override
    public Body deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        final ObjectCodec oc = parser.getCodec();
        final JsonNode node = oc.readTree(parser);

        return deserialize(node);
    }

    private Body deserialize(JsonNode node) {
        final String type = node.get(TYPE).asText();

        if (COMPLEX.equals(type)) {
            return deserializeToBodyWithContent(node, type);
        } else {
            return deserializeToBodyWithString(node, type);
        }
    }

    private Body deserializeToBodyWithString(JsonNode node, String type) {
        final int position = node.get(POSITION).asInt();

        return new Body<String>().setContent(node.get(CONTENT).asText()).setType(type).setPosition(position);
    }

    private Body deserializeToBodyWithContent(JsonNode node, String type) {
        final int position = node.get(POSITION).asInt();
        final String provider = node.get(PROVIDER).asText();
        final String previewImageUrl = node.get(PREVIEW_IMAGE_URL).asText();

        return new Body<Content>().setContent(createContent(node.get(CONTENT)))
                                  .setType(type)
                                  .setProvider(provider)
                                  .setPreviewImageUrl(previewImageUrl)
                                  .setPosition(position);
    }

    private Content createContent(JsonNode node) {
        final int width = node.get(WIDTH).asInt();
        final int height = node.get(HEIGHT).asInt();
        final String vid = node.get(VID).asText();
        final String url = node.get(URL).asText();
        final String caption = node.get(CAPTION).asText();
        final String watchUrl = node.get(WATCH_URL).asText();
        final String embedHtml = node.get(EMBED_HTML).asText();
        final String playerTemplate = node.get(PLAYER_TEMPLATE).asText();

        return new Content().setPlayerTemplate(playerTemplate)
                            .setWidth(width)
                            .setHeight(height)
                            .setUrl(url)
                            .setCaption(caption)
                            .setVid(vid)
                            .setWatchUrl(watchUrl)
                            .setEmbedHtml(embedHtml);
    }
}

这不是最好的解决方案,但它确实有效。我现在使用GSON,它有点简单。