2

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

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

格式一:

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

格式二:

{
  "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 字段的映射?

4

3 回答 3

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 类相关联:

    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 是最好的解决方案,因为它提供了更多的控制。

于 2013-10-18T19:18:19.677 回答
1

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

对于第二部分,关于映射mailemail等,您可以使用 setter

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

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

此处的setMail- 方法将被 Jackson 检测到,并将为mail示例 2 中的属性调用。您可以为您想要的所有映射(联系人、用户、谁)添加此类设置方法。

于 2013-10-17T19:09:38.233 回答
1

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

这样,您就可以在一个类中拥有特定于不同提供者的所有代码,并且可以在需要时轻松添加新提供者。

于 2013-10-18T16:08:33.440 回答