如果我尝试返回从实体类中的字段获取的列表,我会收到以下错误。如果我自己创建列表,那么我不会收到此错误。我正在使用 SpringBoot 3.3.2 和 Java 17。
错误:
{
"message": "Exception occurred: HttpMessageNotWritableException: Could not write JSON: class java.lang.String cannot be cast to class java.util.UUID (java.lang.String and java.util.UUID are in module java.base of loader 'bootstrap')",
}
带有堆栈跟踪的完整错误日志:
{
"@timestamp": "2024-08-10T10:43:56.580637+05:30",
"@version": "1",
"message": "Exception stack trace: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: class java.lang.String cannot be cast to class java.util.UUID (java.lang.String and java.util.UUID are in module java.base of loader 'bootstrap')\n\tat org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:492)\n\tat org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:114)\n\tat org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:297)\n\tat org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:245)\n\tat org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat com.github.foodie.config.FilterConfig.doFilter(FilterConfig.java:26)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)\n\tat java.base/java.lang.Thread.run(Thread.java:840)\nCaused by: com.fasterxml.jackson.databind.JsonMappingException: class java.lang.String cannot be cast to class java.util.UUID (java.lang.String and java.util.UUID are in module java.base of loader 'bootstrap') (through reference chain: com.github.foodie.controllers.response.GenericResponse[\"data\"]->com.github.foodie.dtos.TestDto1[\"testDto2\"]->com.github.foodie.dtos.TestDto2[\"uuidList2\"]->java.util.ArrayList[0])\n\tat com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:402)\n\tat com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:373)\n\tat com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:345)\n\tat com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContentsUsing(IndexedListSerializer.java:148)\n\tat com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:88)\n\tat com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)\n\tat com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)\n\tat com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)\n\tat com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)\n\tat com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)\n\tat com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)\n\tat com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)\n\tat com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)\n\tat com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)\n\tat com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)\n\tat com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)\n\tat com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:502)\n\tat com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:341)\n\tat com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1574)\n\tat com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1061)\n\tat org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:483)\n\t... 51 more\nCaused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.UUID (java.lang.String and java.util.UUID are in module java.base of loader 'bootstrap')\n\tat com.fasterxml.jackson.databind.ser.std.UUIDSerializer.serialize(UUIDSerializer.java:24)\n\tat com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContentsUsing(IndexedListSerializer.java:142)\n\t... 68 more\n",
"logger_name": "com.github.foodie.advices.ExceptionAdvice",
"thread_name": "http-nio-8082-exec-3",
"level": "INFO",
"level_value": 20000,
"correlationId": "1667a21f-358f-4066-a3bb-bc6c19cacb59"
}
控制器类 出于测试目的,我直接从此处调用 OrderRequestEntity,并确保数据库中存在给定 UUID 的实体。您可以在本文中的 TestDto1、TestDto2 和 GenericResponse 之后找到 OrderRequestEntity 类。
@GetMapping("/res1")
private ResponseEntity<GenericResponse<TestDto1>> responseTest1(@RequestParam Integer num) {
TestDto2 testDto2 = new TestDto2();
List<UUID> uuidList = new ArrayList<>();
switch (num) {
case 1: {
uuidList.add(UUID.randomUUID());
uuidList.add(UUID.randomUUID());
break;
}
case 2: {
OrderRequestEntity entity = orderRequestRepository.findById(UUID.fromString("158790c2-ce63-4d00-b053-33f5a72782e7")).orElse(null);
uuidList = entity.getItems();
}
}
testDto2.setId(UUID.randomUUID());
testDto2.setUuidList2(uuidList);
TestDto1 testDto1 = new TestDto1();
testDto1.setId(UUID.randomUUID());
testDto1.setTestDto2(testDto2);
return ResponseEntity.ok(new GenericResponse<>(true, "response got", testDto1));
}
测试Dto1
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDto1 {
private UUID id;
private TestDto2 testDto2;;
}
测试Dto2
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDto2 {
private UUID id;
private List<UUID> uuidList2;
private Point point1;
private Point point2;
}
通用响应
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GenericResponse<T> {
private boolean success;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String message;
private String correlationId = MDC.get("correlationId");
private T data;
public GenericResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}
public GenericResponse(T data) {
this.success = true;
this.data = data;
}
}
OrderRequestEntity(这里我感兴趣的字段是“items”,我为此编写了转换器,当我们将其存储在数据库中时将其转换为 JSON,当我们尝试从中获取项目时将其转换回列表实体)。
@Data
@Entity
@Table(name = "order_request")
public class OrderRequestEntity {
@Id
@GeneratedValue
@Column(name = "id")
private UUID id;
@Column(name = "restaurant_location", columnDefinition = "Geometry(Point, 4326)")
private Point restaurantLocation;
@Column(name = "delivery_location", columnDefinition = "Geometry(Point, 4326)")
private Point deliveryLocation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private CustomerEntity customerEntity;
@Column(name = "customer_id", updatable = false, insertable = false)
private UUID customerId;
@Column(name = "restaurant_id")
private UUID restaurantId;
@Convert(converter = UUIDListConvertor.class)
@Column(name = "items")
private List<UUID> items;
@Enumerated(EnumType.STRING)
@Column(name = "payment_type")
private PaymentType paymentType;
@Enumerated(EnumType.STRING)
@Column(name = "order_request_status")
private OrderRequestStatusType orderRequestStatusType;
@Column(name = "created_on")
private Instant createdOn;
@Column(name = "updated_on")
private Instant updatedOn;
@PrePersist
protected void onCreate() {
this.createdOn = this.updatedOn = Instant.now();
}
@PreUpdate
protected void onUpdate() {
this.updatedOn = Instant.now();
}
}
UUID列表转换器
@Converter
public class UUIDListConvertor extends JsonConverter<List<UUID>> {
}
Json转换器
@Converter
public class JsonConverter<T> implements AttributeConverter<T, String> {
private final TypeReference<T> typeReference = new TypeReference<T>() {};
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(T object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize JSON data", e);
}
}
@Override
public T convertToEntityAttribute(String json) {
try {
return objectMapper.readValue(json, typeReference);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to deserialize JSON data", e);
}
}
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
<version>6.5.2.Final</version>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>8.0</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.15.0</version>
</dependency>
预期结果是 List 在 TestDto2 中正确设置,我们也得到它的响应。但是当我从 OrderRequestEntity 设置它时,我收到上面共享的错误。当我们手动将 uuid 添加到列表中时,不会出现此错误。我怀疑这是由于与 OrderRequestEntity 中的 items 字段关联的转换器所致。
为了检查这是否是问题,我使用了调试器,我观察到在 TestDto2 的列表 uuidList2 中成功设置了值,但当程序到达末尾给出响应时,发生错误。
遵循@JAsgarov的建议,如果我重写UUIDListConvertor中的方法并自己传递正确的TypeReference,那么程序将按预期工作。
这是我在 UUIDListConvertor 中写的内容
@Converter
@Slf4j
public class JsonConverter<T> implements AttributeConverter<T, String> {
private final TypeReference<T> typeReference = new TypeReference<T>() {};
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(T object) {
log.info("converting to database column");
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize JSON data", e);
}
}
@Override
public T convertToEntityAttribute(String json) {
log.info("converting to entity attribute");
try {
return objectMapper.readValue(json, typeReference);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to deserialize JSON data", e);
}
}
但我无法理解为什么 JsonConvertor 中的 T 没有正确评估为
List<UUID>
。这是 TypeReference 在调试器中的样子
遵循@JAsgarov的建议,如果我重写
UUIDListConvertor
中的方法并自己传递正确的TypeReference
,那么程序将按预期工作。问题似乎是 erasure 在 Java 中的工作方式,正如 @JAsgarov 在问题的评论部分所指出的。
这是我在
UUIDListConvertor
中写的内容。
@Converter
@Slf4j
public class JsonConverter<T> implements AttributeConverter<T, String> {
private final TypeReference<T> typeReference = new TypeReference<T>() {};
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(T object) {
log.info("converting to database column");
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize JSON data", e);
}
}
@Override
public T convertToEntityAttribute(String json) {
log.info("converting to entity attribute");
try {
return objectMapper.readValue(json, typeReference);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to deserialize JSON data", e);
}
}