配置XStream以动态映射到不同的对象

时间:2013-01-02 13:55:22

标签: java json jackson xstream oxm

我正在使用RESTful第三方API,它总是以下列格式发送JSON:

{
    "response": {
        ...
    }
}

其中...是需要映射回Java POJO的响应对象。例如,有时JSON将包含应该映射回Fruit POJO的数据:

{
    "response": {
        "type": "orange",
        "shape": "round"
    }
}

...有时JSON将包含应该映射回Employee POJO的数据:

{
    "response": {
        "name": "John Smith",
        "employee_ID": "12345",
        "isSupervisor": "true",
        "jobTitle": "Chief Burninator"
    }
}

因此,根据RESTful API调用,我们需要将这两个JSON结果映射回两个中的一个:

public class Fruit {
    private String type;
    private String shape;

    // Getters & setters for all properties
}

public class Employee {
    private String name;
    private Integer employeeId;
    private Boolean isSupervisor;
    private String jobTitle;

    // Getters & setters for all properties
}

不幸的是,我无法改变这个第三方REST服务总是发送回{ "response": { ... } } JSON结果的事实。但我仍然需要一种方法来配置映射器,以便将response动态映射回FruitEmployee

首先,我尝试Jackson取得了有限的成功,但它并不像我希望的那样可配置。所以现在我尝试使用XStream及其JettisonMappedXmlDriver将JSON映射回POJO。这是我的原型代码:

public static void main(String[] args) {
    XStream xs = new XStream(new JettisonMappedXmlDriver());

    xs.alias("response", Fruit.class);
    xs.alias("response", Employee.class);

    // When XStream sees "employee_ID" in the JSON, replace it with
    // "employeeID" to match the field on the POJO.
    xs.aliasField("employeeID", Employee.class, "employee_ID");

    // Hits 3rd party RESTful API and returns the "*fruit version*" of the JSON.
    String json = externalService.getFruit();

    Fruit fruit = (Fruit)xs.fromXML(json);
}

不幸的是,当我运行它时,我得到一个异常,因为我有xs.alias("response", ...)映射response到两个不同的Java对象:

Caused by: com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$UnknownFieldException: No such field me.myorg.myapp.domain.Employee.type
---- Debugging information ----
field               : type
class               : me.myorg.myapp.domain.Employee
required-type       : me.myorg.myapp.domain.Employee
converter-type      : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path                : /response/type
line number         : -1
version             : null
-------------------------------

所以我问:我该怎样做才能避免API总是发回相同的“包装器”response JSON对象这一事实?我唯一能想到的是首先像这样做一个String-replace:

String json = externalService.getFruit();
json = json.replaceAll("response", "fruit");
...

但这似乎是一个丑陋的黑客。 XStream(或其他映射框架)是否提供了在这种特殊情况下可以帮助我的任何东西? Thansk提前。

2 个答案:

答案 0 :(得分:0)

杰克逊有两种方式:

  • 手动测试所需的密钥(JsonNode具有必要的方法);
  • 使用JSON Schema; Java中有一个API:json-schema-validator(是的,那是我的),它使用了Jackson。

编写与您的第一个对象类型匹配的架构:

{
    "type": "object",
    "properties": {
        "type": {
            "type": "string",
            "required": true
        },
        "shape": {
            "type": "string",
            "required": true
        }
    },
    "additionalProperties": false
}

将其加载为架构,根据它验证您的输入:如果验证,您知道需要对您的水果类进行反序列化。否则,为第二个项类型创建模式,作为安全措施对其进行验证,并使用另一个类反序列化。

还有code examples for the API(版本1.4.x)

答案 1 :(得分:0)

如果你确实知道实际的类型,杰克逊应该相对直截了当。 您需要使用通用包装类型,如:

public class Wrapper<T> {
  public T response;
}

然后唯一的技巧是构造类型对象让杰克逊知道T有什么。 如果它是静态可用的,您只需:

Wrapper<Fruit> wrapped = mapper.readValue(input, new TypeReference<Wrapper<Fruit>>() { });
Fruit fruit = wrapped.response;

但如果它是更动态生成的,例如:

Class<?> rawType = ... ; // determined using whatever logic is needed
JavaType actualType = mapper.getTypeFactory().constructGenericType(Wrapper.class, rawType);
Wrapper<?> wrapper = mapper.readValue(input, actualType);
Object value = wrapper.response;

但不管怎样,它“应该正常”。请注意,在后一种情况下,您可以使用基类型(“?extends MyBaseType”),但通常不能指定动态类型。