2013-10-03 3 views
20

Я использую Flickr API. При вызове метода flickr.test.login, JSON результат по умолчанию:Пользовательская JSON Deserialization с Jackson

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

Я хотел бы разобрать этот ответ в объект Java:

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

В JSON свойства должны быть отображены следующим образом:

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

К сожалению, я не могу найти хороший, элегантный способ сделать это с помощью аннотаций. До сих пор мой подход заключается в том, чтобы прочитать строку JSON в Map<String, Object> и получить значения оттуда.

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

Но я думаю, что это самый не элегантный способ, когда-либо. Есть ли простой способ, используя аннотации или пользовательский десериализатор?

Это было бы очень очевидно для меня, но, конечно, это не работает:

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

ответ

30

Вы можете написать собственный десериализатор для этого класса. Это может выглядеть так:

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

После этого вы должны определить сериализатор для своего класса. Вы можете сделать это следующим образом:

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

Спасибо. Часть, в которой я отсутствовала, была аннотация. Я должен был предоставить аннотацию @JsonDeserialize, несмотря на то, что объект является подклассом типа, зарегистрированного в модуле. – th3morg

0

Вы должны сделать имя пользователя класс в FlickrAccount и дать ему _content поле

+0

Noooooo ...... :-(Это единственный способ? –

+0

JSON представляет имя пользователя в качестве объекта, так что, когда вы карту, он отображает как объект. Это не страшно, хотя, вы может поместить метод getUsername в класс FlickrAccount, который возвращает значение Username._content, поэтому при использовании он будет прозрачным. – tom

+0

Верно, но не то, что я хочу или нуждаюсь. Существует [запрос функции] (http: //jira.codehaus .org/browse/JACKSON-781), описывающий функцию, которая будет делать то, что я хочу, но она все еще открыта. –

6

Поскольку я не «т хочет реализовать собственный класс (Username) только для отображения имени пользователя, я пошел с немного более элегантным, но все же довольно некрасиво подходом:

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

Это все еще не такой элегантный, как я надеялся, но по крайней мере я избавился от всего уродливого кастинга. Другим преимуществом этого решения является то, что мой класс домена (FlickrAccount) не загрязнен никакими аннотациями Джексона.

Основано на @Michał Ziober's answer, я решил использовать - по-моему - самое прямое решение. Использование @JsonDeserialize аннотации с пользовательской десериализацией:

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

Но десериализатор не использует какие-либо внутренние классы, только JsonNode, как описано выше:

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

Вы также можете использовать 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); 
Смежные вопросы