web-dev-qa-db-ja.com

JacksonによるカスタムJSONデシリアライゼーション

Flickr API を使用しています。 flickr.test.login メソッドを呼び出す場合、デフォルトのJSON結果は次のとおりです。

{
    "user": {
        "id": "21207597@N07",
        "username": {
            "_content": "jamalfanaian"
        }
    },
    "stat": "ok"
}

このレスポンスをJavaオブジェクトに解析したい:

public class FlickrAccount {
    private String id;
    private String username;
    // ... getter & setter ...
}

JSONプロパティは次のようにマッピングする必要があります。

"user" -> "id" ==> FlickrAccount.id
"user" -> "username" -> "_content" ==> FlickrAccount.username

残念ながら、注釈を使用してこれを行うための素敵でエレガントな方法を見つけることができません。これまでの私のアプローチは、JSON文字列をMap<String, Object>に読み取り、そこから値を取得することです。

Map<String, Object> value = new ObjectMapper().readValue(response.getStream(),
        new TypeReference<HashMap<String, Object>>() {
        });
@SuppressWarnings( "unchecked" )
Map<String, Object> user = (Map<String, Object>) value.get("user");
String id = (String) user.get("id");
@SuppressWarnings( "unchecked" )
String username = (String) ((Map<String, Object>) user.get("username")).get("_content");
FlickrAccount account = new FlickrAccount();
account.setId(id);
account.setUsername(username);

しかし、これはこれまでで最もエレガントではない方法だと思います。注釈またはカスタムデシリアライザーを使用する簡単な方法はありますか?

これは私には非常に明白ですが、もちろん機能しません:

public class FlickrAccount {
    @JsonProperty( "user.id" ) private String id;
    @JsonProperty( "user.username._content" ) private String username;
    // ... getter and setter ...
}
27
Moritz Petersen

このクラスのカスタムデシリアライザーを作成できます。次のようになります。

class FlickrAccountJsonDeserializer extends JsonDeserializer<FlickrAccount> {

    @Override
    public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        Root root = jp.readValueAs(Root.class);

        FlickrAccount account = new FlickrAccount();
        if (root != null && root.user != null) {
            account.setId(root.user.id);
            if (root.user.username != null) {
                account.setUsername(root.user.username.content);
            }
        }

        return account;
    }

    private static class Root {

        public User user;
        public String stat;
    }

    private static class User {

        public String id;
        public UserName username;
    }

    private static class UserName {

        @JsonProperty("_content")
        public String content;
    }
}

その後、クラスのデシリアライザーを定義する必要があります。これは次のようにして実行できます。

@JsonDeserialize(using = FlickrAccountJsonDeserializer.class)
class FlickrAccount {
    ...
}
35
Michał Ziober

ユーザー名をマッピングするだけのためにカスタムクラス(Username)を実装したくないので、もう少しエレガントになりましたが、それでもかなりいアプローチです。

ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(in);
JsonNode user = node.get("user");
FlickrAccount account = new FlickrAccount();
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());

思ったほどエレガントではありませんが、少なくともIいキャストはすべて取り除きました。このソリューションのもう1つの利点は、私のドメインクラス(FlickrAccount)がJacksonアノテーションで汚染されていないことです。

@MichałZioberの答え に基づいて、私は-私の意見では-最も単純なソリューションを使用することにしました。を使って @JsonDeserializeカスタムデシリアライザーを使用した注釈:

@JsonDeserialize( using = FlickrAccountDeserializer.class )
public class FlickrAccount {
    ...
}

ただし、デシリアライザは内部クラスを使用せず、上記のJsonNodeのみを使用します。

class FlickrAccountDeserializer extends JsonDeserializer<FlickrAccount> {
    @Override
    public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws 
            IOException, JsonProcessingException {
        FlickrAccount account = new FlickrAccount();
        JsonNode node = jp.readValueAsTree();
        JsonNode user = node.get("user");
        account.setId(user.get("id").asText());
        account.setUsername(user.get("username").get("_content").asText());
        return account;
    }
}
8
Moritz Petersen

SimpleModuleを使用することもできます。

    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
    @Override public JsonDeserializer<?> modifyDeserializer(
        DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        if (beanDesc.getBeanClass() == YourClass.class) {
            return new YourClassDeserializer(deserializer);
        }

        return deserializer;
    }});

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(module);
    objectMapper.readValue(json, classType);
1
sendon1982