将具有不同格式的JSON字符串反序列化为同一Java类的实例

时间:2013-10-17 16:46:35

标签: java json jackson deserialization

使用Jackson将具有不同格式的JSON字符串反序列化为同一Java类的实例的最佳方法是什么?

例如,我有关于从不同来源获取的用户的信息:

格式1:

"user" : {
  "name"     : "John",
  "age"      : 21,
  "email"    : "john@mail.com",
  "location" : "NYC"
}

格式2:

{
  "user" : "John",
  "mail" : "john@mail.com",
  "age"  : "21"
}

格式3:

{
  "who"      : "John",
  "contacts" : "john@mail.com",
  "age"      : 21
}

我想将所有这些字符串反序列化为以下类的实例:

public class User {
  public String name;
  public String email;
  public int age;
}

也许有办法通过Map而不是注释定义从JSON字段到Java字段的映射?

3 个答案:

答案 0 :(得分:1)

对于第一部分,无论"user" :是否应该是JSON字符串的一部分,您可以使用Java中的普通字符串操作来操作它。

对于第二部分,关于将mail映射到email等,您可以使用setter

public class User {
  public String name;
  public String email;
  public int age;

  public void setMail(String value) {
     this.email = value;
  }
}

此处的setMail - 方法将由Jackson检测,并将在示例2中调用mail属性。您可以为所需的所有映射添加此类set方法(联系人,用户,谁)。

答案 1 :(得分:1)

将源数据直接反序列化为单一目标格式很快就会变得有点麻烦且容易出错。我建议为不同的源格式定义单独的类,实现指定getUser()方法的接口。此方法将生成所需的目标格式。

通过这种方式,您可以在一个类中拥有特定于不同提供程序的所有代码,并且在需要时可以轻松添加新提供程序。

答案 2 :(得分:1)

我挖掘杰克逊文档,找到了两个解决方案。

这是我的Java类:

public class User {
    protected String name;
    protected String email;
    protected int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return name + ": [ " + email + ", " + age + " ]";
    }
}

首先解决方案是创建自定义PropertyNamingStrategy:

public class MappingPropertyNamingStrategy extends PropertyNamingStrategy {

    Map<String, String> nameMap = new HashMap<String, String>();

    public void setMap(Map<String, String> map) {
        nameMap = map;
    }

    @Override
    public String nameForGetterMethod(MapperConfig<?> cfg,
                                      AnnotatedMethod method,
                                      String defaultName) {
        return mapName(defaultName);
    }
    @Override
    public String nameForSetterMethod(MapperConfig<?> cfg,
                                      AnnotatedMethod method,
                                      String defaultName) {
        return mapName(defaultName);
    }
    @Override
    public String nameForField(MapperConfig<?> cfg,
                               AnnotatedField field,
                               String defaultName) {
        return mapName(defaultName);
    }

    protected String mapName(String name) {
        if (nameMap.containsKey(name)) {
            return nameMap.get(name);
        } else {
            return name;
        }
    }
}

然后,您可以定义从用户字段到适当的JSON字段的映射:

    String json1 = "{ \"user\": { \"name\": \"John\", \"age\": 21, \"email\": \"john@mail.com\", \"location\": \"NYC\" }}";
    String json2 = "{ \"user\": \"John\", \"mail\": \"john@mail.com\", \"age\": \"21\" }";
    String json3 = "{ \"who\": \"John\", \"contacts\": \"john@mail.com\", \"age\": 21 }";

    ObjectMapper mapper = new ObjectMapper();
    MappingPropertyNamingStrategy namingStrategy = new MappingPropertyNamingStrategy();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    Map<String, User> res = mapper.readValue(json1, new TypeReference<Map<String, User>>() {});
    System.out.println(res.get("user"));


    mapper = new ObjectMapper();
    mapper.setPropertyNamingStrategy(namingStrategy);
    namingStrategy.setMap(new HashMap<String, String>() {{
                put("name", "user");
                put("email", "mail");
            }});
    System.out.println(mapper.readValue(json2, new TypeReference<User>(){}));


    mapper = new ObjectMapper();
    mapper.setPropertyNamingStrategy(namingStrategy);
    namingStrategy.setMap(new HashMap<String, String>() {{
                put("name", "who");
                put("email", "contacts");
            }});

    System.out.println(mapper.readValue(json3, new TypeReference<User>(){}));

另一种解决方案是使用mixins:

@JsonIgnoreProperties({"location"})
abstract class FirstFormat {
}

abstract class SecondFormat {
    @JsonProperty("user")
    public abstract void setName(String name);
    @JsonProperty("mail")
    public abstract void setEmail(String email);
    public abstract void setAge(int age);
}

abstract class ThirdFormat {
    @JsonProperty("who")
    public abstract void setName(String name);
    @JsonProperty("contacts")
    public abstract void setEmail(String email);
    public abstract void setAge(int age);
}

然后你只需要将它与User class关联:

    mapper = new ObjectMapper();
    mapper.addMixInAnnotations(User.class, FirstFormat.class);
    Map<String, User> res = mapper.readValue(json1, new TypeReference<Map<String, User>>() {});
    System.out.println(res.get("user"));        

    mapper = new ObjectMapper();
    mapper.addMixInAnnotations(User.class, SecondFormat.class);
    System.out.println(mapper.readValue(json2, new TypeReference<User>(){}));

    mapper = new ObjectMapper();
    mapper.addMixInAnnotations(User.class, ThirdFormat.class);
    System.out.println(mapper.readValue(json3, new TypeReference<User>(){}));

我认为,mixins是最好的解决方案,因为它可以提供更多控制。