2013-10-03 20 views
20

Ich benutze die Flickr API. Wenn die flickr.test.login Methode aufrufen, ist die Standard-JSON Ergebnis:Benutzerdefinierte JSON Deserialisierung mit Jackson

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

Ich mag würde diese Antwort in ein Java-Objekt analysieren:

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

Die JSON Eigenschaften sollte wie folgt zugeordnet werden:

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

Leider kann ich nicht eine nette, elegante Möglichkeit, dies mithilfe von Anmerkungen zu finden. Mein Ansatz ist bisher, den JSON String in eine Map<String, Object> einzulesen und die Werte von dort abzurufen.

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

Aber ich denke, das ist die meisten nicht-elegante Art und Weise, überhaupt. Gibt es einen einfachen Weg, entweder mit Anmerkungen oder einem benutzerdefinierten Deserializer?

Dies würde für mich sehr offensichtlich, aber natürlich ist es nicht funktioniert:

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

Antwort

30

Sie benutzerdefinierte Deserializer für diese Klasse schreiben kann. Es könnte so aussehen:

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

Danach müssen Sie Serializer für Ihre Klasse definieren. Sie können dies auf diese Weise tun:

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

Danke. Der Teil, der mir darin fehlte, war die Anmerkung. Ich musste die Annotation @ JsonDeserialize angeben, obwohl das Objekt eine Unterklasse eines in einem Modul registrierten Typs ist. – th3morg

0

Sie haben Benutzername eine Klasse innerhalb FlickrAccount zu machen und ein _content Feld

+0

Noooooo ...... :-(Ist das der einzige Weg? –

+0

Die JSON den Benutzernamen als Objekt darstellt, so, wenn Sie es abzubilden, es abbildet als Objekt. Es ist allerdings nicht so schlimm, Sie kann eine getUsername-Methode in die FlickrAccount-Klasse einfügen, die den Username._content -Wert zurückgibt, so dass sie transparent wäre – tom

+0

Richtig, aber nicht das, was ich will oder brauche.Es gibt eine [feature request] (http: //jira.codehaus .org/browse/JACKSON-781) beschreibt ein Feature, das tun würde, was ich will, aber es ist immer noch offen. –

6

Da ich don geben ‚t wollen eine benutzerdefinierte Klasse (Username) implementieren, einfach den Benutzernamen auf der Karte, ich mit ein bisschen mehr elegant ging, aber immer noch ziemlich hässlich Ansatz:

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

Es ist immer noch nicht so elegant, wie ich gehofft hatte, aber wenigstens habe ich all die hässlichen Castings losgeworden. Ein weiterer Vorteil dieser Lösung ist, dass meine Domain-Klasse (FlickrAccount) nicht mit Jackson-Anmerkungen belastet ist.

Basierend auf @Michał Ziober's answer entschied ich mich, die - meiner Meinung nach - einfachste Lösung zu verwenden. Mit Hilfe eines @JsonDeserialize Annotation mit einem benutzerdefinierten Deserializer:

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

Aber der Deserializer verwendet keine internen Klassen, die nur JsonNode wie oben:

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

Sie können auch SimpleModule verwenden.

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