web-dev-qa-db-fra.com

Désérialisation JSON personnalisée avec Jackson

J'utilise API Flickr . Lorsque vous appelez le flickr.test.login , le résultat JSON par défaut est:

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

Je voudrais analyser cette réponse dans un objet Java:

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

Les propriétés JSON doivent être mappées comme ceci:

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

Malheureusement, je ne suis pas en mesure de trouver une manière agréable et élégante de le faire en utilisant Annotations. Jusqu'à présent, mon approche est de lire la chaîne JSON dans un Map<String, Object> et obtenir les valeurs à partir de là.

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);

Mais je pense que c'est la manière la plus élégante qui soit. Existe-t-il un moyen simple d'utiliser des annotations ou un désérialiseur personnalisé?

Ce serait très évident pour moi, mais bien sûr, cela ne fonctionne pas:

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

Vous pouvez écrire un désérialiseur personnalisé pour cette classe. Cela pourrait ressembler à ceci:

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;
    }
}

Après cela, vous devez définir un désérialiseur pour votre classe. Vous pouvez le faire comme suit:

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

Comme je ne veux pas implémenter une classe personnalisée (Username) juste pour mapper le nom d'utilisateur, je suis allé avec une approche un peu plus élégante, mais toujours assez laide:

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());

Ce n'est toujours pas aussi élégant que je l'espérais, mais au moins je me suis débarrassé de tout le laid casting. Un autre avantage de cette solution est que ma classe de domaine (FlickrAccount) n'est pas polluée par des annotations Jackson.

Sur la base de @ Réponse de Michał Ziober , j'ai décidé d'utiliser la - à mon avis - la solution la plus simple. Utilisant un @JsonDeserialize annotation avec un désérialiseur personnalisé:

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

Mais le désérialiseur n'utilise aucune classe interne, juste le JsonNode comme ci-dessus:

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

Vous pouvez également utiliser 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