java - 是否可以设置 Jackson 以将未命名的包装器对象排除到 API 返回的 JSON 数组中?
问题描述
我正在开发一个 Spring Boot 应用程序,我发现将 JSON 对象(通过使用 RestTemplate 执行的 REST 调用检索)转换为域对象时存在一些问题。
这是我的主要域对象:
public class NotaryDistrict {
String idDistrict;
String denominazione;
String regione;
String provincia;
ArrayList<Localita> localita;
String distretto;
String indirizzo;
String cap;
String telefono;
String fax;
String email;
String pec;
String webUrl;
ArrayList<Carica> cariche;
public NotaryDistrict() {
super();
}
public NotaryDistrict(String idDistrict, String denominazione, String regione, String provincia,
ArrayList<Localita> localita, String distretto, String indirizzo, String cap, String telefono, String fax,
String email, String pec, String webUrl, ArrayList<Carica> cariche) {
super();
this.idDistrict = idDistrict;
this.denominazione = denominazione;
this.regione = regione;
this.provincia = provincia;
this.localita = localita;
this.distretto = distretto;
this.indirizzo = indirizzo;
this.cap = cap;
this.telefono = telefono;
this.fax = fax;
this.email = email;
this.pec = pec;
this.webUrl = webUrl;
this.cariche = cariche;
}
public String getIdDistrict() {
return idDistrict;
}
public void setIdDistrict(String idDistrict) {
this.idDistrict = idDistrict;
}
public String getDenominazione() {
return denominazione;
}
public void setDenominazione(String denominazione) {
this.denominazione = denominazione;
}
public String getRegione() {
return regione;
}
public void setRegione(String regione) {
this.regione = regione;
}
public String getProvincia() {
return provincia;
}
public void setProvincia(String provincia) {
this.provincia = provincia;
}
public ArrayList<Localita> getLocalita() {
return localita;
}
public void setLocalita(ArrayList<Localita> localita) {
this.localita = localita;
}
public String getDistretto() {
return distretto;
}
public void setDistretto(String distretto) {
this.distretto = distretto;
}
public String getIndirizzo() {
return indirizzo;
}
public void setIndirizzo(String indirizzo) {
this.indirizzo = indirizzo;
}
public String getCap() {
return cap;
}
public void setCap(String cap) {
this.cap = cap;
}
public String getTelefono() {
return telefono;
}
public void setTelefono(String telefono) {
this.telefono = telefono;
}
public String getFax() {
return fax;
}
public void setFax(String fax) {
this.fax = fax;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPec() {
return pec;
}
public void setPec(String pec) {
this.pec = pec;
}
public String getWebUrl() {
return webUrl;
}
public void setWebUrl(String webUrl) {
this.webUrl = webUrl;
}
public ArrayList<Carica> getCariche() {
return cariche;
}
public void setCariche(ArrayList<Carica> cariche) {
this.cariche = cariche;
}
@Override
public String toString() {
return "NotaryDistrict [idDistrict=" + idDistrict + ", denominazione=" + denominazione + ", regione=" + regione
+ ", provincia=" + provincia + ", localita=" + localita + ", distretto=" + distretto + ", indirizzo="
+ indirizzo + ", cap=" + cap + ", telefono=" + telefono + ", fax=" + fax + ", email=" + email + ", pec="
+ pec + ", webUrl=" + webUrl + ", cariche=" + cariche + "]";
}
}
如您所见,它包含此数组字段:
ArrayList<Carica> cariche;
这是给我带来问题的字段(如果我排除这个评论它,它工作正常......其他字段已正确映射)
这是 Carica 域对象:
public class Carica {
String idNotary;
String nome;
String cognome;
String carica;
public Carica() {
super();
// TODO Auto-generated constructor stub
}
public Carica(String idNotary, String nome, String cognome, String carica) {
super();
this.idNotary = idNotary;
this.nome = nome;
this.cognome = cognome;
this.carica = carica;
}
public String getIdNotary() {
return idNotary;
}
public void setIdNotary(String idNotary) {
this.idNotary = idNotary;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCognome() {
return cognome;
}
public void setCognome(String cognome) {
this.cognome = cognome;
}
public String getCarica() {
return carica;
}
public void setCarica(String carica) {
this.carica = carica;
}
@Override
public String toString() {
return "NotaryPosition [idNotary=" + idNotary + ", nome=" + nome + ", cognome=" + cognome + ", carica=" + carica
+ "]";
}
}
在我的业务逻辑代码中,我以这种方式执行 API 调用:
ResponseEntity forEntity2 = restTemplate.getForEntity(uri, NotaryDistrict.class); NotaryDistrict notaryDistrictDetails = forEntity2.getBody();
System.out.println("notaryDistric 详细信息:" + notaryDistrictDetails);
{
"idDistrict": "CG7drXn9fvA%253D",
"distretto": "SCIACCA",
"denominazione": "Agrigento e Sciacca",
"provincia": "Agrigento",
"regione": "Sicilia",
"indirizzo": "Viale della Vittoria n.319",
"cap": "92100",
"telefono": "092220111",
"fax": "09222111",
"email": "xxx@yyy.it",
"pec": "zzzz@postacertificata.yyy.it",
"webUrl": null,
"cariche": [
{
"carica": {
"idNotary": "e12oYuuTvE4%253D",
"nome": "Claudia",
"cognome": "Rossi",
"carica": "Presidente"
}
},
{
"carica": {
"idNotary": "XlB2DSwWbfE%253D",
"nome": "Maria",
"cognome": "Verdi",
"carica": "Segretario"
}
},
{
"carica": {
"idNotary": "W8I4vogJ0OM%253D",
"nome": "Giuseppe",
"cognome": "Bianchi",
"carica": "Tesoriere"
}
},
{
"carica": {
"idNotary": "DR6Y%252BA37%252Few%253D",
"nome": "ARIANNA",
"cognome": "Ciani",
"carica": "Consigliere"
}
},
]
}
因此,除了 cariche 数组之外的所有字段都正确映射到我的 NotaryDistrict 主域对象中。
当我添加 ArrayList cariche 时出现问题;域对象。
我希望每个进入 cariche JSON 数组的对象都必须用一个对象映射到我班级的 cariche 数组中。
但我得到了这个例外:
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (PushbackInputStream); line: 1, column: 363] (through reference chain: com.notariato.updateInfo.domain.NotaryDistrict["cariche"]->java.util.ArrayList[0]->com.notariato.updateInfo.domain.Carica["carica"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.12.4.jar:2.12.4]
这个异常的原因对我来说很清楚:问题是名为cariche的 JSON 数组包含一个包装器对象{...},它本身包含一个carica对象。
我认为一个可能的解决方案是创建一个二级包装域对象,但它非常难看。
存在一种设置 Jackson 以忽略此{...}包装器对象并仅考虑其内容的方法,即必须映射到此 Java 数组的carica对象:
ArrayList<Carica> cariche;
解决方案
一种方法是编写自定义反序列化器。(事实上,如果 Ralph 的评论是正确的,这是目前唯一的方法。)您确实需要在应用程序中再添加一个类,但它是一个短类。
下面,您将找到一个包含此类反序列化器的 Spring Boot 测试。正如评论所说,它不是生产就绪的代码,但测试通过了,修改它来做你想做的事情应该是相当容易的。您可能想了解如何编写 Jackson 反序列化器;如果您找到任何好的资源,请在评论中发布,因为据我所知不存在。感谢 Eugen Paraschiv,我引用了他的一篇典型的简洁文章。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
// non-static imports omitted for brevity
@RestClientTest
class SampleApplicationTests {
@Autowired MockRestServiceServer server;
@Autowired RestTemplate template;
@TestConfiguration static class Config {
@Bean RestTemplate template(@Autowired RestTemplateBuilder builder) { return builder.build(); }
}
@Data @EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor static class ElementEntity {
String key;
}
@Data static class NiceEntityWithList { List<ElementEntity> list; }
@SuppressWarnings("serial") static class WeirdEntityDeserializer extends StdDeserializer<ElementEntity> {
protected WeirdEntityDeserializer() { this(null); }
protected WeirdEntityDeserializer(Class<?> vc) { super(vc); }
@Override public ElementEntity deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec codec = p.getCodec();
JsonNode weirdElementNode = codec.readTree(p);
JsonNode realElementNode = weirdElementNode.get("common");
// in a real app, handle the case where realElementNode turns out to be null
// we punt to a Jackson method here so this deserializer uses as much of the rest of
// your Jackson configuration as possible
return codec.treeToValue(realElementNode, ElementEntity.class);
// in a real app, handle the case where realElementNode.get("key") is null
// (you may also want to do some sort of validation here)
}
}
@Data static class WeirdEntityWithList {
@JsonDeserialize(contentUsing = WeirdEntityDeserializer.class) List<ElementEntity> list;
}
@Test void niceListDeserializes() {
final String niceJson = "{\"list\": [{\"key\": \"value\"}, {\"key\": \"value2\"}]}";
this.server.expect(requestTo("/nicelist")).andRespond(withSuccess(niceJson, MediaType.APPLICATION_JSON));
NiceEntityWithList nice = template.getForEntity("/nicelist", NiceEntityWithList.class).getBody();
assertEquals(new ElementEntity("value"), nice.getList().get(0));
// could just use nice.list, but let's pretend we're writing
// code for real and the class is in another package
assertEquals(new ElementEntity("value2"), nice.getList().get(1));
}
@Test void weirdListDeserializes() {
final String weirdJson = "{\"list\": [{\"common\": {\"key\": \"value\"}}, {\"common\": {\"key\": \"value2\"}}]}";
server.expect(requestTo("/weirdlist")).andRespond(withSuccess(weirdJson, MediaType.APPLICATION_JSON));
WeirdEntityWithList weird = template.getForEntity("/weirdlist", WeirdEntityWithList.class).getBody();
assertEquals(new ElementEntity("value"), weird.getList().get(0));
assertEquals(new ElementEntity("value2"), weird.getList().get(1));
}
}
这应该适用于从 start.spring.io 下载的项目;您需要添加 spring-starter-web 和 lombok 作为依赖项,并放入spring.main.web-application-type: none
application.properties。
注意:如果我@JsonDeserialize
从WeirdEntityWithList
上面的定义中删除,测试确实会失败,但它们不会像您的应用程序失败那样失败——也不例外;列表元素的字段只是设置为空。我怀疑这与 Spring Boot 的默认 Jackson 配置和您的配置之间的一些差异有关。(您可能还使用了不同的 Spring Boot 版本等)我希望导致差异的任何原因都不会使代码对您无用。
推荐阅读
- javascript - 是否可以使用 vanilla js 接收 http 请求?
- c++ - 我的程序在完成所有执行之前就中断了
- python - 实现一个没有 async/asyncio 的 Python WebSocket 监听器
- node.js - NPM 安装卡在 fetchMetadata 或 rollbackFailedOptional 或 sill IdealTree buildDeps
- node.js - 我们如何将 Graphiql Api 的响应存储到 MongoDB 中?
- c++ - 记住一个随机选择的值
- tortoisegit - Windows 10 上用户自定义设置 TortoiseGIT 的位置在哪里?
- sharepoint - Powerautomate Flow:sharepoint 列表名称突然更改
- javafx - 如果 ObservableList 项目减少,JavaFX TableView 背景颜色不会恢复默认值
- mysql - 为什么查询不返回值?