2014-12-03 57 views
3

我目前正在尝试开发与某个在线服务进行通信的REST客户端。这个在线服务返回一些我希望映射到Java对象的JSON响应,使用Jackson将JSON映射为具有单属性和多属性的多态POJO

JSON响应的一个例子是:

{ 
    "id" : 1, 
    "fields" : [ { 
     "type" : "anniversary", 
     "value" : { 
     "day" : 1, 
     "month" : 1, 
     "year" : 1970 
     } 
    }, { 
     "type" : "birthday", 
     "value" : { 
     "day" : 1, 
     "month" : 1, 
     "year" : 1970 
     } 
    }, { 
     "type" : "simple", 
     "value" : "simple string" 
    },{ 
     "type": "name", 
     "value": { 
      "firstName": "Joe", 
      "lastName": "Brown" 
     } 
    } ] 
} 

注意以下几点:

  • 该结构包含一个简单的ID,和场实例的集合,每一个都具有类型
  • 结构由外部公式确定operty
  • 在给出的例子中
  • ,有3种类型 - >日期,名称,及单值串
  • 生日周年类型二者匹配日期结构
  • 有几类映射到一个字符串值,如电子邮件类型,twitterId类型,公司型等

我的问题是,我似乎并没有能够正确地映射这种结构的Java对象

这是我到目前为止已经完成。以下是课程和他们的杰克逊注释(吸气剂和吸附剂被省略)。

public class Contact { 
    private int id; 
    private List<Field> fields; 
} 

public class Field { 
    private FieldType type; 
    private FieldValue value; 
} 

public enum FieldType { 
    EMAIL, NICKNAME, NAME, ADDRESS, BIRTHDAY, ANNIVERSARY 
} 

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type", 
     defaultImpl = SingleFieldValue.class) 
@JsonSubTypes({ 
     @JsonSubTypes.Type(value = NameFieldValue.class, name = "name"), 
     @JsonSubTypes.Type(value = DateFieldValue.class, name = "anniversary"), 
     @JsonSubTypes.Type(value = DateFieldValue.class, name = "birthday"), 
     @JsonSubTypes.Type(value = SingleFieldValue.class, name = "nickname"), 
     @JsonSubTypes.Type(value = SingleFieldValue.class, name = "email"), 
     //other types that map to SingleFieldValue 
}) 
public abstract FieldValue { 
} 


public class NameFieldValue extends FieldValue { 

    private String firstName; 
    private String lastName; 
} 

public class DateFieldValue extends FieldValue { 

    private int day; 
    private int month; 
    private int year; 
} 

public class SingleFieldValue extends FieldValue { 

    private String value; 
} 

ObjectMapper不包含任何配置,则使用默认配置。

你有什么建议要正确映射这些?我想避免制作自定义的反序列化器,而只是遍历Json对象,比如JsonNode。

备注:对于缺乏足够明确的信息,我提前道歉。请说明我的配方有任何问题。

+0

你有没有考虑过简单地写一些软件来做到这一点? – 2014-12-03 13:05:35

+0

我的愿望是使用杰克逊解开JSON。我想这就是你所指的。 – 2014-12-03 13:12:14

+0

将'value'作为Map并对其进行排序。你可以离开休息。 – 2014-12-03 13:13:36

回答

2

您已经使用FieldValue级别的抽象类在FIeld类中使用它。在这种情况下,您可以构造类型为= email和value = address的对象,这可能会导致一些问题...

我建议为具有特定FieldValue类型的每种类型创建特定的类。从/ 下面的代码序列化/反序列化JSON所需的格式从/到POJO:

public class Main { 
    String json = "{\"id\":1,\"fields\":[{\"type\":\"SIMPLE\",\"value\":\"Simple Value\"},{\"type\":\"NAME\",\"value\":{\"firstName\":\"first name\",\"lastName\":\"last name\"}}]}"; 

    public static void main(String []args) throws IOException { 
     ObjectMapper objectMapper = new ObjectMapper(); 

     String json = objectMapper.writeValueAsString(generate()); 
     System.out.println(json); 

     System.out.println(objectMapper.readValue(json, Contact.class)); 
    } 

    private static Contact generate() { 
     SimpleField simpleField = SimpleField.builder().type(FieldType.SIMPLE).value("Simple Value").build(); 

     NameFieldValue nameFieldValue = NameFieldValue.builder().firstName("first name").lastName("last name").build(); 
     NameField nameField = NameField.builder().type(FieldType.NAME).value(nameFieldValue).build(); 

     return Contact.builder().id(1).fields(Arrays.asList(simpleField, nameField)).build(); 
    } 
} 

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type") 
@JsonSubTypes({ 
     @JsonSubTypes.Type(value = SimpleField.class, name = "SIMPLE"), 
     @JsonSubTypes.Type(value = NameField.class, name = "NAME") 
}) 
interface Field { 
    FieldType getType(); 
    Object getValue(); 
} 

enum FieldType { 
    SIMPLE, NAME 
} 

@Data 
@Builder 
@AllArgsConstructor 
@NoArgsConstructor 
class Contact { 
    private int id; 
    private List<Field> fields; 
} 

@Data 
@Builder 
@AllArgsConstructor 
@NoArgsConstructor 
class SimpleField implements Field { 
    private FieldType type; 
    private String value; 

    @Override 
    public FieldType getType() { 
     return this.type; 
    } 

    @Override 
    public String getValue() { 
     return this.value; 
    } 
} 

@Data 
@Builder 
@AllArgsConstructor 
@NoArgsConstructor 
class NameField implements Field { 
    private FieldType type; 
    private NameFieldValue value; 

    @Override 
    public FieldType getType() { 
     return this.type; 
    } 

    @Override 
    public Object getValue() { 
     return this.value; 
    } 
} 

@Data 
@Builder 
@AllArgsConstructor 
@NoArgsConstructor 
class NameFieldValue { 
    private String firstName; 
    private String lastName; 
} 

我用龙目岛库这里只是为了尽量减少代码,避免创建getter/setter方法以及构造函数。您可以删除lombok注释并添加getters/setters /构造函数,并且代码的工作原理相同。

所以,这个想法是,你有一个Contact类(这是你的JSON的根)与一个字段列表(其中字段是一个接口)。每个Field类型都有自己的实现,如NameField实现Field并将NameFieldValue作为属性。这里的技巧是你可以改变getValue()方法声明并声明它返回通用接口或对象(我使用Object但接口也可以)。

该解决方案不需要任何定制串行器/解串器,并且易于维护。

+0

对不起,等待。但这确实帮助我达成了一个解决方案。有一点需要注意,在这种情况下,include的值现在是JsonTypeInfo.As.PROPERTY,因为它与Field对象处于同一级别。非常感谢你! – 2014-12-08 18:18:13