2013-10-03 22 views
20

Sto usando il Flickr API. Quando si chiama il metodo flickr.test.login, il risultato di default è JSON:Deserializzazione JSON personalizzata con Jackson

{ 
    "user": { 
     "id": "[email protected]", 
     "username": { 
      "_content": "jamalfanaian" 
     } 
    }, 
    "stat": "ok" 
} 

vorrei analizzare questa risposta in un oggetto Java:

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

Le proprietà JSON dovrebbero essere mappati in questo modo:

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

Sfortunatamente, non sono in grado di trovare un modo elegante per farlo utilizzando Annotazioni. Il mio approccio finora è, leggere la stringa JSON in un Map<String, Object> e ottenere i valori da 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); 

Ma penso, questo è il modo più non elegante, mai. C'è un modo semplice, usando Annotazioni o Deserializzatore personalizzato?

Questo sarebbe molto evidente per me, ma ovviamente non funziona:

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

risposta

30

È possibile scrivere deserializzatore personalizzato per questa classe. Potrebbe essere il seguente:

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

Dopodiché, è necessario definire il serializzatore per la classe. È possibile farlo in questo modo:

@JsonDeserialize(using = FlickrAccountJsonDeserializer.class) 
class FlickrAccount { 
    ... 
} 
+1

Grazie. La parte che mi mancava in questa era l'annotazione. Ho dovuto fornire l'annotazione @JsonDeserialize nonostante l'oggetto sia una sottoclasse di un tipo registrato in un modulo. – th3morg

0

Devi fare Nome utente una classe all'interno FlickrAccount e dargli un campo _content

+0

Noooooo ...... :-(è che l'unico modo? –

+0

Il JSON rappresenta il nome utente come un oggetto, in modo che quando si esegue il mapping, è mappe come un oggetto. Non è terribile, però, si può mettere un metodo getUsername nella classe FlickrAccount che restituisce il valore Username._content in modo da renderlo trasparente – tom

+0

Vero, ma non quello che voglio o necessario.C'è una [richiesta di funzionalità] (http: //jira.codehaus .org/browse/JACKSON-781) che descrive una funzione, che farebbe ciò che voglio, ma è ancora aperta. –

6

Dal momento che don 't vuole implementare una classe personalizzata (Username) solo per mappare il nome utente, sono andato con un po' più elegante, ma ancora abbastanza brutto approccio:

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

E ' ancora non elegante come speravo, ma almeno mi sono liberato di tutto il brutto casting. Un altro vantaggio di questa soluzione è che la mia classe di dominio (FlickrAccount) non è inquinata da nessuna annotazione di Jackson.

Sulla base di @Michał Ziober's answer, ho deciso di utilizzare la soluzione, a mio avviso, più semplice. Utilizzando un'annotazione @JsonDeserialize con un deserializzatore personalizzato:

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

Ma il deserializzatore non utilizzare tutte le classi interne, solo il JsonNode come sopra:

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

È inoltre possibile utilizzare 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);