java - Deserialización JSON personalizada con Jackson
flickr (4)
Estoy usando la API de Flickr . Al llamar al método flickr.test.login
, el resultado JSON predeterminado es:
{
"user": {
"id": "21207597@N07",
"username": {
"_content": "jamalfanaian"
}
},
"stat": "ok"
}
Me gustaría analizar esta respuesta en un objeto Java:
public class FlickrAccount {
private String id;
private String username;
// ... getter & setter ...
}
Las propiedades JSON se deben asignar de esta manera:
"user" -> "id" ==> FlickrAccount.id
"user" -> "username" -> "_content" ==> FlickrAccount.username
Desafortunadamente, no puedo encontrar una forma agradable y elegante de hacer esto usando Anotaciones. Mi enfoque hasta ahora es leer la Cadena JSON en un Map<String, Object>
y obtener los valores desde allí.
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);
Pero creo que esta es la forma más no elegante, nunca. ¿Hay alguna forma simple, ya sea utilizando Anotaciones o un Deserializador personalizado?
Esto sería muy obvio para mí, pero por supuesto no funciona:
public class FlickrAccount {
@JsonProperty( "user.id" ) private String id;
@JsonProperty( "user.username._content" ) private String username;
// ... getter and setter ...
}
Como no quiero implementar una clase personalizada (nombre de Username
) solo para asignar el nombre de usuario, opté por un enfoque un poco más elegante, pero aún muy feo:
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());
Todavía no es tan elegante como esperaba, pero al menos me deshice de todo el reparto feo. Otra ventaja de esta solución es que mi clase de dominio ( FlickrAccount
) no está contaminada con ninguna anotación de Jackson.
Basándome en la respuesta de @ Michał Ziober , decidí utilizar la solución más sencilla en mi opinión. Usando una anotación @JsonDeserialize
con un deserializador personalizado:
@JsonDeserialize( using = FlickrAccountDeserializer.class )
public class FlickrAccount {
...
}
Pero el deserializador no usa ninguna clase interna, solo el JsonNode
como anteriormente:
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;
}
}
Debes convertir el nombre de usuario en una clase dentro de FlickrAccount y asignarle un campo de contenido
Puede escribir deserializador personalizado para esta clase. Podría verse así:
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;
}
}
Después de eso, debes definir un deserializador para tu clase. Puede hacerlo de la siguiente manera:
@JsonDeserialize(using = FlickrAccountJsonDeserializer.class)
class FlickrAccount {
...
}
También puedes usar 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);