Cannot deserialize instance of `com.example.million.model.Domain` out of START_ARRAY token - spring

I'm using Kotlin, Spring Boot, Jackson dataformat csv. Not sure, how I can return response from my csv as List of domain objects. and I have the following error
Cannot deserialize instance of com.example.million.model.Domain out of START_ARRAY token
My code is the following:
#Service
class DomainService {
fun getDomains(): List<Domain> {
val mapper = CsvMapper()
mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY)
val csvFile = File("myCsv.csv")
val response: List<Domain> = mapper.readerFor(Domain::class.java).readValues<Domain>(csvFile).readAll()
return response
}
}
data class Domains(var domain: String){}

Deleting this line, as otherwise you are wrapping each csv line in an array, which leads to your error message.
mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY)
Your code otherwise looks fine.

Related

Problem with Kotlin inline class deserialization to JSON in tests

I'm trying to write tests for routes in a WebFlux application, but I encountered a problem. I got an error during parsing to JSON:
13:38:20.601 [parallel-1] DEBUG org.springframework.web.server.handler.ResponseStatusExceptionHandler - [248c6c90] Resolved [ServerWebInputException: "400 BAD_REQUEST "Failed to read HTTP message"; nested exception is org.springframework.core.codec.DecodingException: JSON decoding error: Cannot construct instance of `com.test.GetItemRequest` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.test.GetItemRequest` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)<EOL> at [Source: (org.springframework.core.io.buffer.DefaultDataBuffer$DefaultDataBufferInputStream); line: 1, column: 2]"] for HTTP POST /v1/download
I have a request that uses an inline class:
#JvmInline
value class ItemName(val rawValue: String)
data class GetItemRequest(val name: ItemName)
The test:
#Test
fun `Request is parsed successfully`() {
//...
val client = WebTestClient.bindToRouterFunction(router.eimApiRoutes()).build()
val request = """{"name":"item1"}"""
val resp = client
.post()
.uri(EimApiRouter.DOWNLOAD_PATH)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(request), String::class.java)
.exchange()
.expectStatus()
.isOk
.expectBody()
.returnResult()
.responseBody
val expectedResponse = "OK"
assertEquals(expectedResponse, String(resp!!))
}
I can work the problem around by adding a default constructor:
data class GetItemRequest(val name: ItemName) {
constructor() : this(ItemName(""))
}
When I change the type of the parameter to String, it works.
The question:
Could somebody give the real solution? Maybe there is something missing in the test web client configuration (I tried to configure the Kotlin codecs manually, but without success)?

How to change WebClient behaviour concerning LocalDateTime?

I am using Spring Boot 2.4.5 and try to request a REST API of another application using WebClient. I know that the other application provides the requested information as a Collection.
When I use an Object[] to receive the response:
Object[] ob = (Object[]) webClient
.get()
.uri(endpoint)
// .bodyValue(criteria)
.exchangeToMono(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
return response.bodyToMono(Object[].class);
} else if (response.statusCode()
.is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
}).block();
I can see that I receive a LinkedHashMap with all the values including a field:
date_of_declaration -> 2020-03-02T08:43:10
However, if possible, I want to let WebClient immediately convert the response into the designated DTOs...
DeclarationDTO[] ob = (DeclarationDTO[]) webClient
.get()
.uri(endpoint)
// .bodyValue(criteria)
.exchangeToMono(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
return response.bodyToMono(DeclarationDTO[].class);
} else if (response.statusCode()
.is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
}).block();
...I get an exception when a LocalDateTime object shall be deserialized.
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type `java.time.LocalDateTime` from String "02-03-2020 01:20:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '02-03-2020 01:20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {HourOfAmPm=1, NanoOfSecond=0, SecondOfMinute=0, MicroOfSecond=0, MinuteOfHour=20, MilliOfSecond=0},ISO resolved to 2020-03-02 of type java.time.format.Parsed; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "02-03-2020 01:20:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '02-03-2020 01:20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {HourOfAmPm=1, NanoOfSecond=0, SecondOfMinute=0, MicroOfSecond=0, MinuteOfHour=20, MilliOfSecond=0},ISO resolved to 2020-03-02 of type java.time.format.Parsed
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1438] (through reference chain: java.lang.Object[][0]->de.xxx.myportal.api.infrastructure.dto.MyDTO["a_person"]->de.xxx.myportal.api.infrastructure.dto.APersonDTO["foobar"])
I think WebClient has an internal ObjectMapper, so maybe it is possible to modify this ObjectMapper during WebClient instantiation? ...or is there a better way to tell Webclient how to handle LocalDateTime? ...maybe a WebClient customized by a configuration or...?
I can't explain but after removing any JsonFormat annotation it works out of the box. (Weird!)
This may help https://www.baeldung.com/spring-boot-formatting-json-dates
In your DeclarationDTO you can use something like:
#JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
#JsonProperty("date_of_declaration")
private LocalDateTime dateOfDeclaration;

Spring Boot REST endpoint not returning JSON

I have the following code:
case class X(s: String)
#RequestMapping(path = Array("/tagReads"), produces = Array("application/json"))
def tagReads(#RequestParam(value = "tagId") tagId:String): X = {
val response = X("Hello")
println(response)
response
}
curl -H "Accept: application/json localhost:8080/tagReads?tagId="1234" results in exactly what I would expect being println-ed in the spring boot app, but the response I get is {}.
If I change the return to just "Hello" and the type to String then I get "Hello" returned when I curl.
I don't like that I'm getting empty JSON the rest of the time. I am using spring-boot-starter-web:2.1.6-RELEASEand this is all wrapped in an #RestController annotated class.
There is nothing useful logged to the Spring Boot application console.
I'm stuck - all the examples I've seen suggest this should 'just work' - so any help is much appreciated
In order to support Scala Case Classes serialization, try to add the jackson-module-scala as your dependency and register the Scala module with the Jackson ObjectMapper e.g.
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)

Spring Boot - RabbitMQ - Changing message content type from octet-stream to json

I having issue getting from my Byte[] message received in RabbitMQ converted into some object based on a model.
I'm using Spring Boot.
In the application I have implemented RabbitListenerConfigurer:
#SpringBootApplication
class Application : RabbitListenerConfigurer {
...
override fun configureRabbitListeners(registrar: RabbitListenerEndpointRegistrar) {
registrar.messageHandlerMethodFactory = messageHandlerMethodFactory()
}
#Bean
fun messageHandlerMethodFactory(): MessageHandlerMethodFactory {
val messageHandlerMethodFactory = DefaultMessageHandlerMethodFactory()
messageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter())
return messageHandlerMethodFactory
}
#Bean
fun consumerJackson2MessageConverter(): MappingJackson2MessageConverter {
return MappingJackson2MessageConverter()
}
}
I then have a listener (the println is just for the time being for me to check) like:
#RabbitListener(queues = ["success.queue"])
fun receivedSuccessMessage(data: MyModel) {
println(data)
}
And the model itself looks like (again simplified just for testing):
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel(
val input: Any,
val metadata: Any
)
Now whenever that queue gets a message the error i get is:
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.models.MyModel] for GenericMessage [payload=byte[1429]
Also to add the message I'm receving into that success queue looks like this:
{"input":"somestuff", "metadata": "somestuff"}
And I also just noticed that the message type is coming in as content_type: application/octet-stream. This is out of my control as thats just what I am receiving from the service that puts the message in there:
Any ideas why this is - I assumed that RabbitListenerConfigurer implementation I have does the conversion for all messages from byte array into whatever model I specify.
Any help/ideas would be appreciated.
Thanks.
UPDATE
Ok I got this working, however I'm quite confused about something:
This is what I added in:
#Autowired
lateinit var connectionFactory : ConnectionFactory
#Bean
fun rabbitListenerContainerFactory(): SimpleRabbitListenerContainerFactory {
val factory = SimpleRabbitListenerContainerFactory()
factory.setConnectionFactory(connectionFactory)
factory.setAfterReceivePostProcessors(SomeBusiness())
return factory
}
And then the actual conversion like this:
class SomeBusiness : MessagePostProcessor {
#Throws(AmqpException::class)
override fun postProcessMessage(message: Message): Message {
println("WE WENT IN NOW")
println()
if (message.messageProperties.contentType == "application/octet-stream") {
message.messageProperties.contentType = "application/json"
}
return message
}
}
However the thing I don't understand is I already have my messageHandlerMethodFactory function which I assumed did all the work around listening to messages. Now that I have rabbitListenerContainerFactory is there any concern of anything conflicting. Or is it perfectly valid, namely I need rabbitListenerContainerFactory as it implements SimpleRabbitListenerContainerFactory which gives me access to the method setAfterReceivePostProcessors.
Again, any explanation would really be appreciated.
Thanks
As you are receiving content_type: application/octet-stream and this can't changed.
You could use Spring Interceptor Or Servlet Filter to convert the content type application/octet-stream to application/json.
Example using HandlerInterceptor for Spring Boot Hope this helps. Thanks
Please find the below source which is working for me,
public void convertByteArrayToObject(Message<?> message) {
Object request = message.getPayload();
Object result = null;
try {
byte[] byteArray = (byte[]) request;
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
result = new ObjectInputStream(byteArrayInputStream).readObject();
} catch (ClassNotFoundException | IOException e) {
LOG.error(e.getMessage());
}
return result;
}
I had the same problem. Solution below helped me to solve it.
Solution:
Spring understand only "contentType", not "content_type". So, just set "contentType" property for your message, for example (in my case message producer was not spring app):
String json = ...;
TextMessage textMessage = session.createTextMessage(json);
textMessage.setStringProperty("contentType", "application/json");
Explanation:
Class MappingJackson2MessageConverter has method canConvertFrom(Message<?> message, #Nullable Class<?> targetClass). That method checks if headers contains supported mime types. And if to dive deeper, we will find that AbstractMessageConverter.getMimeType(MessageHeaders header) has contentTypeResolver, who gets appropriate property. Instance of that object located in CompositeMessageConverterFactory, it's DefaultContentResolver, which gets content type in method resolve(MessageHeaders headers):
Object contentType = headers.get(MessageHeaders.CONTENT_TYPE);
Where public static final String CONTENT_TYPE = "contentType";

Parse multiple JSON entries using org.springframework.boot.json.JsonParser

I'd like to parse a JSON Array using the native JsonParser available in Spring Boot:
String url="https://restservice.com";
RestTemplate restTemplate = new RestTemplate();
String resp = restTemplate.getForObject(url, String.class);
JsonParser springParser = JsonParserFactory.getJsonParser();
Map<String, Object> map = springParser.parseMap(resp);
That works if there's just one item in the Array. If multiple items are returned, an exception is thrown:
Caused by: org.springframework.boot.json.JsonParseException: Cannot parse JSON
at org.springframework.boot.json.AbstractJsonParser.tryParse(AbstractJsonParser.java:60) ~[spring-boot-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.boot.json.JacksonJsonParser.parseMap(JacksonJsonParser.java:55) ~[spring-boot-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at com.example.samplewebapp.DemoApplication.lambda$demo$2(DemoApplication.java:50) [classes!/:0.0.1-SNAPSHOT]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:813) [spring-boot-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
... 13 common frames omitted
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token
What is the correct API to return a List of Map JSON objects?
Showing the JSON you are trying to parse would be helpful for getting a good answer to your problem.
Anyway, if you are trying to get a list you should use the parseList() method instead. This required doing a cast from objects to maps, but worked fine for me otherwise. Here's a quick example:
String json = "[{\"key\":\"value1\"}, {\"key\":\"value2\"}]";
JsonParser springParser = JsonParserFactory.getJsonParser();
List<Object> list = springParser.parseList(json);
for(Object o : list) {
if(o instanceof Map) {
Map<String,Object> map = (Map<String,Object>) o;
//do processing here
}
}
However, I would suggest calling the desired parser (Jackson, gson, etc.) directly if you need more control over the process. Jackson provides a handy ObjectMapper class to help with this sort of thing and avoid messy type conversions.

Resources