JsonParseException: Unexpected character ('i' (code 105)): was expecting double-quote - java

I am trying to integrate Spring Cloud streams and publishing a custom Java Object across services with RabbitMQ as broker. The object I am publishing looks like:
public class AppMessageEnvelope implements Serializable {
...
private Object messageBody;
private Date sentAt = new Date();
...
// setters and getters
}
This is just a wrapper object and the original object is put in messageBody. The object I am putting in messageBody looks like:
public class Job {
...
private String message;
private Map<MyEnum, String> myMap;
...
}
Note that both AppMessageEnvelope and Job are in a different model project which is imported as a Maven dependency in the publisher and subscriber Spring Boot projects, so models are exactly the same.
In producer, I publish the object as:
#EnableBinding(Source.class)
public class JobDistributor {
private final Source jobQueue;
#Autowired
public JobDistributor(Source jobQueue) {
this.jobQueue = jobQueue;
}
public AppMessageEnvelope publishJob(AppMessageEnvelope message) {
LOG.info("Sending message: {}.", message);
jobQueue.output().send(MessageBuilder.withPayload(message).build());
return message;
}
}
In consumer, I get the message as:
#Component
#EnableBinding(Sink.class)
public class JobConsumer {
private final JobManager jobManager;
private final ObjectMapper objectMapper;
#Autowired
public JobConsumer(
JobManager jobManager, ObjectMapper objectMapper) {
this.jobManager = jobManager;
this.objectMapper = objectMapper;
}
#StreamListener(target = Sink.INPUT)
public void processData(AppMessageEnvelope messageEnvelope) {
LOG.info("Envelope received: {}.", messageEnvelope);
try {
TypeReference<Job> mapType = new TypeReference<Job>() {};
Job job = objectMapper.readValue(messageEnvelope.getMessageBody().toString(), mapType);
jobManager.processRequest(job);
} catch (Exception ex) {
LOG.error("Couldn't convert to correct object for processing: {}.", ex);
}
}
}
I try to use TypeReference to convert the internal object into a correct object but I get an error as:
JobConsumer - Couldn't convert to correct object for processing: {}.
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('i' (code 105)): was expecting double-quote to start field name
at [Source: (StringReader); line: 1, column: 3]
Before the message is converted, I log it:
JobConsumer - Envelope received: AppMessageEnvelope{..., messageBody={id=5bf3a7302dbe9c7cf9927c60, jobId=8c0bfcb0b21248e694b5cd52337a1f9e, submittedAt=2018-11-20T06:18:24+0000, lastUpdatedOn=null, message=null, ..., fileContentMap={FILE_BYTES=JVBERi0xLjUKJb/3ov}}, sentAt=Tue Nov 20 11:48:24 IST 2018}
I tried configuring ObjectMapper as:
#Autowired
private ObjectMapper objectMapper() {
JsonFactory factory = new JsonFactory();
factory.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
return new ObjectMapper(factory);
}
I tried enabling unquoting fields with this too:
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
I tried solutions provided by this blog and some similar SO problem but nothing solved. What am I missing?

Use below json conversion to convert JSON to Model / Entity
ObjectMapper objectMapper = new ObjectMapper();
Job job = objectMapper.convertValue(messageEnvelope.getMessageBody().toString(), Job.class);

This may be funny error please copy your json and past into online json formatter.
if the validation wrong please rewrite the json manually or encode it.
Error cause: character encoding issue at time of copy from various sources

Here the problem:
Job job = objectMapper.readValue(messageEnvelope.getMessageBody().toString(), mapType);
Just like this:
Job job = objectMapper.readValue(objectMapper.writeValueAsString(messageEnvelope.getMessageBody()), mapType);

Related

ArrayObject in API Response along with status and message

I want the API Response to be as Follows:
{"success":"false/true","msg":"some message","data":{}}
if there is data the response data should print in "data":{}
ApiResponse Class
public ApiResponse(Boolean success, String message,JSONObject data) {
this.success = success;
this.message = message;
this.setData(data);
}
Data Returning in Controller
JSONObject dataObject = new JSONObject(user);
return new ResponseEntity(new ApiResponse(false, "User is Disabled",dataObject , HttpStatus.UNAUTHORIZED);
Spring boot would internally uses jackson objectmapper to serialize the object.
You can specify to include the fields even if they are null by using this property when you create the objectMapper bean
#Bean
ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json()
.serializationInclusion(JsonInclude.Include.ALWAYS) // Include even empty values in the json
.build();
}

How to return LocalDateTime from REST controller endpoint?

I'm writing a REST controller in Spring- java, and one of my endpoints should return a LocalDateTime variable.
#GetMapping(path = "/lastDateTimeOfReset")
LocalDateTime fetchLastDateTimeOfReset() {
return mongoManagerService.fetchLastDateTimeOfReset();
}
This is the client request:
ResponseEntity<LocalDateTime> lastDateEntity = restTemplate.getForEntity(url, LocalDateTime.class);
I get the following excepation on my client side:
org.springframework.web.client.RestClientException:
Error while extracting response for type [class java.time.LocalDateTime] and content type [application/json;charset=UTF-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error: Expected array or string.;
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Expected array or string.
at [Source: (PushbackInputStream); line: 1, column: 1]
I checked the return value on debug mode and it is a valid LocalDateTime.
Why is this exception thrown and how to overcome it?
You can use String to overcome this issue.
#GetMapping(path = "/lastDateTimeOfReset")
String fetchLastDateTimeOfReset() {
return (mongoManagerService.fetchLastDateTimeOfReset()).toString();
}
Client Request:
ResponseEntity<String> lastDateEntity = restTemplate.getForEntity(url, String.class);
You need to check return type of mongoManagerService.fetchLastDateTimeOfReset();
Or Test your application with below sample code
Service
#GetMapping(path = "/lastDateTimeOfReset")
LocalDateTime fetchLastDateTimeOfReset() {
return LocalDateTime.now();
}
Client
RestTemplate rs = new RestTemplate();
ResponseEntity<LocalDateTime> lastDateEntity = rs.getForEntity(new URI("http://localhost:8089/lastDateTimeOfReset"), LocalDateTime.class);
System.out.println(lastDateEntity);
Output
<200,2020-02-19T15:00:51.603220,[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Wed, 19 Feb 2020 09:30:51 GMT"]>
Problem solved!
In general- I should've added this configuration to my app:
#Configuration
public class MvcConfig {
#Configuration
#EnableWebMvc
public class MessageConvertersConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}
#Bean
public ObjectMapper objectMapper() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH:mm:ss");
SimpleModule localDateModule = new SimpleModule();
localDateModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
localDateModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
ObjectMapper mapper = new ObjectMapper();
mapper.registerModules(localDateModule);
return mapper;
}
}
}
* IMPORTANT!!! - In Tests add this to your rest template: *
restTemplate.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter(objectMapper())));

Error in org.springframework.web.client.RestClientException: No HttpMessageConverter for java.util.HashMap

I'm getting the following error in org.springframework.web.client.RestClientException: No HttpMessageConverter for java.util.HashMapwhen using rest template. Does anyone have any idea whats wrong?
Using java 1.8, spring 5.2.2, commons-logging 1.2.
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.client.RestTemplate;
public class testapi {
private static final String API_BASE_URL = "https://xyz/rest/ng";
private static RestTemplate template = new RestTemplate();
public static void main(String[] args)
throws Exception {
String token = login();
}
private static String login() {
Map<String, Object> payload = new HashMap<>();
payload.put("loginName", "abc.com");
payload.put("password", "xyz");
Map<String, Object> resp = template.postForObject(getUrl("/sessions"), payload, Map.class);
return (String) resp.get("token");
}
}
Error
Exception in thread "main" org.springframework.web.client.RestClientException: No HttpMessageConverter for java.util.HashMap
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:964)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:421)
at TestFormApis.login(testapis.java:117)
at TestFormApis.main(testapis.java:61)
You are using Spring's web client RestTemplate in the non-Spring environment (at least, your example demonstrates that you run this test in the standard Java way).
If you start a Spring application, it takes most configuration to itself, including adding converters (default converters from package org.springframework.http.converter.*: ByteArrayHttpMessageConverter, StringHttpMessageConverter, ResourceHttpMessageConverter, AllEncompassingHttpMessageConverter, MappingJackson2XmlHttpMessageConverter, MappingJackson2HttpMessageConverter). Last one - for your case, RestTemplate uses inner Jackson for converting.
So, if you want to use your sample, just add the specific converter.
More easier way - to add Jackson library and convert manually:
var mapper = ObjectMapper();
var strPayload = mapper.writeValueAsString(payload);
And use strPayload in RestTempate calls.
Or just start your application as Spring one.
Run the app as Spring one and do the conversion using the code below. I had to include the jackson-core-2.2.0-rc1.jar libs to my project as well.
RestTemplate template = new RestTemplate();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
public String login() {
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM));
template.getMessageConverters().add(mappingJackson2HttpMessageConverter);
Map<String, Object> payload = new HashMap<>();
payload.put("loginName", "xyz");
payload.put("password", "abc");
Map<String, Object> resp = template.postForObject(getUrl("/abc"), payload, Map.class);
return (String) resp.get("token");
}

How to save a Map<String, LocalDateTime> to DynamoDB?

I'm trying to add a Map<String, LocalDateTime> attribute to an already existing DAO. Seems that AWS SDK doesn't know how to convert that since I keep getting this error :
.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
I wrote a DynamoDBTypeConverter to try to resolve this, but it's not showing the data in the correct format:
public static class StringLocalDateTimeMapConverter
implements DynamoDBTypeConverter<String, Map<String, LocalDateTime>> {
#Override
public String convert(Map<String, LocalDateTime> map) {
try {
if (map != null) {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(map);
} else {
throw new Exception("map is empty");
}
} catch (Exception e) {
LOGGER.error(String.format("Error converting map to Dynamo String. Reason - {%s}",
e.getMessage()));
return "";
}
}
#Override
public Map<String, LocalDateTime> unconvert(String string) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(string, Map.class);
} catch (Exception e) {
LOGGER.error(String.format("Error unconverting Dynamo String to map. Reason - {%s}",
e.getMessage()));
return new HashMap<>();
}
}
}
However, this doesn't look as desired - in DDB the map ended up looking like:
{\"1234567890\":{\"year\":2019,\"month\":\"SEPTEMBER\",\"monthValue\":9,\"dayOfMonth\":20,\"hour\":15,\"minute\":13,\"second\":26,\"nano\":98000000,\"dayOfWeek\":\"FRIDAY\",\"dayOfYear\":263,\"chronology\":{\"calendarType\":\"iso8601\",\"id\":\"ISO\"}}}
I'm not sure what the best way to model this data so DDB is happy with it. Any suggestions?
maybe parse the localDateTime to string before saving to DDB and make sure to parse it back to LocalDateTime once reading to from DDB
You can save it in ISO format
This seem's an issue with the ObjectMapper and not with DynamoDb or with your CustomMapper.
I suppose that you use the Jackson provided by the AWS library, probably you need to add the JavaTimeModule into your project and then.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
And all works fine.
This the link for the library
https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310

JSON Response Has Escaped Quotations Using Jackson and JAX-RS Exception Mapper

I have a simple requirement where, if application encounters an exception, my JAX-RS Rest endpoint should return a custom JSON response with 500 HTTP header status.
Data needed to construct the response comes from an object with several properties (see below). The problem is, I am only interested in one or two values from each property (out of several dozens). And I cannot modify any of these models/classes (some have a Jackson annotation for JSON processing, e.g. null properties should be discarded during serialization).
public class MainObject {
private FirstProperty firstProperty;
private SecondProperty secondProperty;
private ThirdProperty thirdProperty;
// other codes
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
public class FirstProperty {
private boolean bol = true;
private double dob = 5.0;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class SecondProperty {
private String str;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class ThirdProperty {
private int intProp = true;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
The expected JSON that I should be seeing coming back is on the client side (say, a browser -- testing in Edge):
{
"firstProperty" : { "subProperty" : [ "val1" ] },
"secondProperty" : { "str" : "val2", "subproperty" : [ "val3", "val6" ] },
"thirdProperty" : { "subProperty" : [ "val4" ] }
}
Instead, all my field names and their values have their quotations escaped, and extra double quotes around the entire value, e.g.:
{
"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }",
"secondProperty" : "{ \"str\" : \"val2\", \"subproperty\" : [ \"val3\", \"val6\" ] }",
"thirdProperty" : "{ \"subProperty\" : [ \"val4\" ] }"
}
Please note the extra " before and after the curly brackets. My environment is:
Java 1.8.45
FasterXML Jackson 2.9.8
Spring Boot 2.0.1
RestEasy (JBoss) JAX-RS
JBoss 6.4
I eliminated the majority of "noise" in the code to see at what point this happens. This is the controller:
#Path("/")
public class MainController {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/rest/path")
public MainObject getMainObject throws MyCustomException {
// A service call that throws MyCustomException
}
}
And JAX-RS ExceptionMapper where I send the response back:
#Provider
public class MyCustomExceptionMapper extends ExceptionMapper<MyCustomException> {
#Override
public Response toResponse(MyCustomException ex) {
HashMap<String, Object> responseBody = new HashMap<>();
String strEx = ex.getStrEx(); // Comes from SecondProperty.str stored in MyCustomException, not that it matters
// Instantiate an empty object that contains
MainObject obj = new MainObject();
obj.getFirstProperty().setSubProperty(ex.getStrs());
obj.getSecondProperty().setStr(strEx);
obj.getSecondProperty().setSubProperty(ex.getStrs());
obj.getThirdProperty().setSubProperty(ex.getStrs());
responseBody.put("firstProperty", serializeFirstProperty(obj.getFirstProperty()));
responseBody.put("secondProperty", serializeSecondProperty(obj.getSecondProperty()));
responseBody.put("thirdProperty", serializeThirdProperty(obj.getThirdProperty()));
Response response = Response.status(/* 500 status */).entity(responseBody).build();
return response;
}
}
Since I only need to send back a very small subset of overall properties from each of my types, I created a custom StdSerializer that would only populate a needed property. For brevity, I only do serializeFirstProperty() but they are all more or less identical:
private StdSerializer<FirstProperty> getFPSerializer(FirstProperty firstProperty) {
return new StdSerializer<FirstProperty>(FirstProperty.class) {
#Override
public void serialize(FirstProperty value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
if (/* there are items in FirstProperty.subProperty */) {
gen.writeArrayFieldStart("subProperty");
for (String str : value.getSubProperty()) {
gen.writeString(str);
}
gen.writeEndArray();
}
gen.writeEndObject();
}
}
private <T> ObjectMapper getCustomOM(StdSerializer<?> serializer) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addSerializer(serializer);
mapper.registerModule(module);
return mapper;
}
Then use these helper methods like:
private String serializeFirstProperty(FirstProperty firstProperty) {
ObjectMapper mapper = getCustomOM(getFPSerializer(firstProperty));
String ser = null;
try { ser = mapper.writeValueAsString(firstProperty); }
catch (JsonProcessingException e) { return null; }
return ser;
}
I have tried countless of configurations with ObjectMapper, e.g. disable(JsonParser.Feature.ALLOW_BACKLASH_ESCAPING_ANY_CHARACTER) (couldn't find any relevant flag for JsonGenerator which I really want to disable in a similar fashion).
Or explicitly returning Object from serializeFirstProperty(), or replacing all the \" with " in serializeFirstProperty() when ser is returned.
Or set custom StdSerializer's JsonGenerator.setCharacterEscapes(new CharacterEscapes() { //... } or play around with JAX-RS Response at no avail. I always seem to get a "string" value with quotations, e.g.:
"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }"
If I simply just do
responseBody.put("firstProperty", mapper.writeValueAsString(obj.getFirstProperty()));
somehow this produces the right JSON output, however, it includes a lot of unnecessary properties which I don't want in this exception handling case.
Funny thing is, when I peer into response (or responseBody map), everything looks right (I don't see values having double quotations).
Please also note that not only I can't modify the models, but some of their properties are instantiated during creation with default values, so not-null inclusion doesn't work, and they will appear in the final JSON if I don't use a custom serialization.
Does anyone know what's causing this escaped and extra quotations?
I think I misunderstood the question in the first attempt of answering it.
The problem is that you serialize a property as string (using mapper.writeValueAsString(this) and then add it to the responseBody which you think of being string to json object map but it is a string to Java object map. In your case at runtime it is a string mapping to another string (the serialized json object is represented as Java string) and a Java string is a Java object as well.
What you want to do instead is to construct a Java object responseBody instead of a map. It should act as a DTO having all the specific properties etc and then serializing it in in one action using the mapper. Because if you first serialize a property to a json string than it it is just a string from the Java point of view and the mapper has no chance to interpret it as a json object.

Categories

Resources