使用Jackson进行自定义JSON反序列化

52

我正在使用Flickr API。调用flickr.test.login 方法时,默认的JSON结果如下:

{
    "user": {
        "id": "21207597@N07",
        "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 ...
}
6个回答

65

您可以为此类编写自定义反序列化器。代码可能如下所示:

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
1
Baeldung提供了一个更简短的示例,无需创建内部静态类来模拟JSON架构。 - ruhong

17

由于我不想实现一个自定义类(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)没有被任何Jackson注释所污染。

基于@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;
    }
}

6
您也可以使用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);

5

我是这样做的:

public class FlickrAccount {
  private String id;
  @JsonDeserialize(converter = ContentConverter.class)
  private String username;
  
  private static class ContentConverter extends StdConverter<Map<String, String>, String> {
    @Override
    public String convert(Map<String, String> content) {
      return content.get("_content"));
    }
  }
}

我实际上正在使用Kotlin,并且想要自定义反序列化类的一个属性,但是没有任何答案能够帮助我(我能够为整个类编写自定义答案,但不仅仅是一个属性)。但是这个答案对我非常有效。谢谢! - Kancha

0
Sharing a code snippet for implementing custom Deserializer 
Basically here we are stopping the user to enter decimal point for integer type for rest api call --->>

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;

import java.io.IOException;

public class CustomIntegerDeserializer extends JsonDeserializer<Integer> {

    private static Logger logger = LoggerFactory.getLogger(CustomIntegerDeserializer.class);

    @Override
    public Integer deserialize(JsonParser p, DeserializationContext ctx)
         throws IOException,JsonProcessingException {
        logger.info("CustomIntegerDeserializer..............................");
        Double doubleVal = p.getDoubleValue();
        try {
            double ceil = Math.ceil(doubleVal);
            double floor = Math.floor(doubleVal);
            if((ceil-floor)>0) {
                String message = String.format("Cannot coerce a floating-point value ('%s') into Integer", doubleVal.toString());
                throw new Exception(message);
            }
            return Integer.valueOf(p.getIntValue());
        } catch (Exception exception) {
            logger.info("Exception in CustomIntegerDeserializer::{}",exception.getMessage());
      
            //throw new custom exception which are to be managed by exception handler
            
        }
    }

}


using on bean class-->
 @JsonDeserialize(using = CustomIntegerDeserializer.class)
    private Integer initRatePerSec = -1;

request-->
{
    .....
    "initRatePerSec": 5.1
}
response-->
{
    "statusCode": 400,
     ..............,
    "message": "JSON parse error: Cannot coerce a floating-point value ('5.1') into Integer; 
}

-2
你需要将Username作为FlickrAccount中的一个类,并赋予它一个_content字段。

JSON表示用户名为一个对象,因此在映射时它会被映射为一个对象。虽然这不是很糟糕,但你可以在FlickrAccount类中添加一个getUsername方法,该方法返回Username._content的值,以便在使用时它是透明的。 - tom
是真的,但不是我想要或需要的。有一个功能请求描述了一个功能,可以做到我想要的,但它仍然是开放的。feature request - Moritz Petersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接