2

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 フィールドへのマッピングを定義する方法があるのではないでしょうか?

4

3 に答える 3

1

Jackson のドキュメントを掘り下げたところ、2 つの解決策が見つかりました。

これが私の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>(){}));

別の解決策は、ミックスインを使用することです。

@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>(){}));

ミックスインはより多くの制御を提供するため、最良のソリューションだと思います。

于 2013-10-18T19:18:19.677 に答える
1

ソース データを単一のターゲット形式に直接逆シリアル化すると、すぐに少し面倒でエラーが発生しやすくなります。メソッドを指定するインターフェイスを実装して、さまざまなソース形式に対して個別のクラスを定義することをお勧めしますgetUser()。このメソッドは、目的のターゲット形式を生成します。

このようにして、さまざまなプロバイダーに固有のすべてのコードを 1 つのクラスに含めることができ、必要に応じて新しいプロバイダーを簡単に追加できます。

于 2013-10-18T16:08:33.440 に答える
1

最初の部分については、JSON 文字列の一部であるかどうかにかかわらず"user" :、Java の通常の文字列操作を使用して操作できます。

2番目の部分のマッピングなどについてはmailemailセッターを使用できます

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

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

ここでのsetMail-method は Jackson によって検出されmail、例 2 のプロパティに対して呼び出されます。必要なすべてのマッピング (連絡先、ユーザー、誰) に対して、そのような set-method を追加できます。

于 2013-10-17T19:09:38.233 に答える