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
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)?
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;
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)
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";
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.