2013-10-03 19 views
20

Używam Flickr API. Po wywołaniu metody flickr.test.login domyślny JSON wynik to:Niestandardowe odserializowanie JSON z Jacksonem

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

Chciałbym przeanalizować tę odpowiedź w obiekt Java:

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

Właściwości JSON powinny być mapowane następująco:

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

Niestety, nie jestem w stanie znaleźć przyjemnego, eleganckiego sposobu, aby to zrobić za pomocą Adnotacji. Do tej pory moim podejściem było odczytanie łańcucha JSON w postaci Map<String, Object> i pobranie z niego wartości.

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

Ale myślę, że jest to najbardziej nie-elegancki sposób, kiedykolwiek. Czy jest jakiś prosty sposób, korzystając z Adnotacji lub niestandardowego Deserializatora?

Byłoby to bardzo oczywiste dla mnie, ale oczywiście to nie działa:

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

Odpowiedz

30

można napisać niestandardowy Deserializator dla tej klasy. Może wyglądać następująco:

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

Następnie należy zdefiniować serializer dla swojej klasy. Możesz to zrobić w ten sposób:

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

Dziękuję. Częścią, której mi brakowało, była adnotacja. Musiałem podać adnotację @JsonDeserialize, mimo że obiekt jest podklasą typu zarejestrowanego w module. – th3morg

0

Trzeba dokonać Nazwa użytkownika klasę ciągu FlickrAccount i nadać mu _content pole

+0

NOOOOOO ...... :-(Czy to jedyny sposób? –

+0

JSON reprezentuje nazwę użytkownika jako przedmiot, więc kiedy go map, mapuje jako obiekt. To nie jest straszna choć, można umieścić metodę getUsername w klasie FlickrAccount, która zwraca wartość Username._content, więc w użyciu będzie przezroczysta – tom

+0

Prawda, ale nie to, co chcę lub potrzebuję Jest to [żądanie funkcji] (http: //jira.codehaus .org/browse/JACKSON-781) opisujące funkcję, która zrobiłaby to, co chcę, ale wciąż jest otwarta. –

6

odkąd don „t chcą wprowadzić niestandardową klasę (Username) tylko mapowanie nazwy użytkownika, poszedłem z trochę bardziej elegancki, ale nadal dość brzydki podejście:

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

To wciąż nie tak elegancko, jak się spodziewałem, ale przynajmniej pozbyłem się tego paskudnego rzucania. Kolejną zaletą tego rozwiązania jest to, że moja klasa domeny (FlickrAccount) nie jest zanieczyszczona żadnymi adnotacjami Jacksona.

Na podstawie @Michał Ziober's answer postanowiłem użyć - moim zdaniem - najbardziej prostoliniowego rozwiązania. Korzystanie z @JsonDeserialize adnotacji o niestandardowym Deserializatora:

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

Ale Deserializator nie używa żadnych klas wewnętrznych, tylko JsonNode jak powyżej:

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

Możesz również użyć 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);