如何将LinkedHashMap转换为自定义的Java对象?

34

我试图通过 RESTful WS 从一个应用程序获取数据并传递到另一个应用程序,它可以正常工作,但是我无法使用这些数据,因为我无法将其强制转换为所需类型... WS 返回的对象列表如下:

{id=1, forename=John, surname=Bloggs, username=jbloggs, role=Graduate Developer, office=London, skills=[{technology=Java, experience=2.5}, {technology=Web, experience=2.0}, {technology=iOS, experience=0.0}, {technology=.NET, experience=0.0}]}

要获取它,使用Jackson的ObjectMapper:

ObjectMapper mapper = new ObjectMapper();

    List<ConsultantDto> list = new ArrayList<ConsultantDto>();


    try {

        list = mapper.readValue(con.getInputStream(), ArrayList.class);

    } catch (JsonGenerationException e) {

        e.printStackTrace();

    } catch (JsonMappingException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }

之后我有三行代码:

System.out.println(list.get(0));
System.out.println(list.get(0).getForename());
return list;

返回是因为该方法的返回值传递给其他Web服务,以在浏览器中显示正确的数据。有趣的事情发生在两个打印行中,一个打印来自此帖子顶部的数据({id:1 ...}),但另一个抛出异常:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.xxx.xxx.web.dto.rp.ConsultantDto
ConsultantDto 和 SkillDto 是两个合法的类,它们的所有属性都设置为与 WS 中的数据匹配,所有的 getters/setters 都已经就位。据我所知,LinkedHashMap 将东西存储为键值对,所以我不知道这个异常是从哪里来的。我该如何修复它?为什么 ObjectMapper 不能正确地解析值(当我获得单个 ConsultantDto 而不是 List 时,它确实可以)?
3个回答

49

你需要做这件事:

List<ConsultantDto> myObjects =
    mapper.readValue(jsonInput, new TypeReference<List<ConsultantDto>>(){});

(来自这个SO的答案)

你必须使用TypeReference, 是因为Java存在一个不幸的怪异问题。如果Java有合适的泛型,我敢打赌你的语法本来就可以工作。


1
@Enno 是否可以使用通用的 T 类型来实现相同的功能? - Stephane
2
使用通用类型 T,使用以下代码: Class<T> type; List<T> myObjects = mapper.readValue(jsonInput,mapper.getTypeFactory().constructCollectionType(ArrayList.class, type)); - Datz
对于泛型类型:List<T>,其中T是MyObjectClass类.... ''' List<MyObjectClass> myObjects = mapper.readValue(json,mapper.getTypeFactory().constructCollectionType(ArrayList.class, MyObjectClass.class)); ''' - pravingaikwad07

7

导入:

import com.fasterxml.jackson.databind.ObjectMapper;

对象:

private ObjectMapper mapper = new ObjectMapper();

例子:

PremierDriverInfoVariationDTO premierDriverInfoDTO = 
mapper.convertValue(json, PremierDriverInfoVariationDTO.class); 
log.debug("premierDriverInfoDTO : {}", premierDriverInfoDTO);

或者。
Map<String, Boolean> map = mapper.convertValue(json, Map.class);
log.debug("result : {}", map);
assertFalse(map.get("eligiblePDJob"));

0
在我的使用案例中,我有10个不同的API。所有API响应都以相同的JSON格式返回,但其中一个标签(/result/data)的内容不同。
{
    "success": true,
    "result": {
        "type": "transactions",
        "data": {}
    },
    "error": [],
    "metadata": {
        "version": "v1",
        "code": 200,
        "desc": "OK",
        "trackingId": "TRACKINGID-1588097123800-1234567890",
        "generatedTimestamp": "2020-07-14T09:41:06.198Z"
    },
    "link": {
        "self": "/v1/myapp/customer/enquiry"
    }
}

在我的Spring Boot代码中,我使用了以下通用方法来处理所有API调用 -
<T, R> ApiResponse<R> execute(URI uri, T entity, Class<R> clazz) {
    HttpEntity<T> httpEntity = new HttpEntity<>(entity);
    ApiResponse<R> body = this.restTemplate.exchange(uri, HttpMethod.POST, httpEntity,
            new ParameterizedTypeReference<ApiResponse<R>>() {
            }).getBody();
    return body;
}

它给了我与您上面报告的相同错误。基于此和其他一些SO答案,我也尝试了下面的方法,但它没有起作用 -

<T, R> ApiResponse<R> execute(URI uri, T entity, Class<R> clazz) {
    HttpEntity<T> httpEntity = new HttpEntity<>(entity); 
    ResponseEntity<String> response = this.scVaultCoreApi.exchange(uri, HttpMethod.POST, httpEntity, String.class);
    ObjectMapper mapper = new ObjectMapper();
    try {
        return mapper.readValue(response.getBody(), new TypeReference<ApiResponse<R>>() {
        });
    } catch (Exception e) {
        throw new RuntimeException();
    }
}

另一个可行的解决方案是使用jackson中的ObjectMapper手动解析响应流。但是,我无法为我正在使用的许多API执行此操作,并且上述错误仅在某些API调用中出现。因此,仅针对这些API,我没有更改我定义的上述方法,而是将ApiResponse<T>提取为ApiResponse<Object>,并仅将其解析为LinkedHashMap,然后手动创建我的特定类对象。
ApiResult<MyResponse> res = execute(uri, payload, MyResponse.class).getResult();
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) res.getData();
MyResponse myResponse = MyResponse.builder()
    .accountBalance(new BigDecimal(map.get("key").get("balance").toString()))
    .clientName((String) map.get("key").get("clientName"))
    .build();
    

这个解决方案最好的地方是我不需要改变基类execute方法,其他API调用也正常工作,只为有问题的API编写了手动对象创建代码。

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