Jersey + Gson not deserializing java.util.Date - java

I'm having a strange issue with a little servlet which uses Jersey and Gson for the JSON serialization/deserialization. I actually copy-pasted the basic Gson provider written for Jersey, like this one: http://eclipsesource.com/blogs/2012/11/02/integrating-gson-into-a-jax-rs-based-application/ and everything seemed to work fine, until I tried to deserialize a Date (in the standard ISO 8601 format), which always gets mapped into my POJO as null.
My first try was to register a deserializer type adapter before returning the gsonBuilder instance, like that:
import java.util.Date;
...
gsonBuilder.registerTypeAdapter(Date.class,
new JsonDeserializer<Date>() {
#Override
public Date deserialize(JsonElement json, Type type,
JsonDeserializationContext arg2) throws JsonParseException {
try {
System.out.println(json);
return (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX")).parse(json.getAsString());
} catch (ParseException e) {
return null;
}
}
});
This didn't work, and nothing is printed out when I send the POST request. I tried to use the setDateFormat method on the gsonBuilder instance before returning it, but this didn't change anything.
I thought there were some others classes implementing the MessageBodyWriter and MessageBodyReader overriding my own implementation, so I tried to delete my own implementation and Jersey complained that it wasn't able to deserialize the JSON (so there are no other providers, i guess).
I tried to set breakpoints in the readFrom method in my MessageBodyReader but the request is actually deserialized without suspending the execution.
I should mention that my class contains different fields too, some strings and one date: the string are always deserialized correctly.
I tried sending different dates, starting with 2016-06-23T00:00:00.000+0200 (which should be formatted with the date format string I used in the code above), and getting to the simple 2016-06-17 by removing one part at the time, and it never worked.
I cleaned my maven project, recompiled it and it didn't work.
I thought it could have been Jetty not loading the correct classes, so i deployed the same code into a Tomcat 8 server, and the result was the same.
My last try was to write another parallel MessageBodyReader but instead of making it generic for the Object type, I made a specific java.util.Date deserializer, and still the readFrom method seems not to be called.
I seriously don't know what I could try now, do you have any idea?
Thanks in advance.

The reason of the error is here...
try {
System.out.println(json);
return (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX")).parse(json.getAsString());
} catch (ParseException e) {
return null;
}
to be more specific here:
"yyyy-MM-dd'T'HH:mm:ss.SSSXX"
java Date and SimpleParser can hold at the most only 3 places for the milliseconds, and there is no wildcard .SSSXX in the string used for the SimpleDateFormat, so your parser is throwing an Exception, that you are catching but returning a Date referenced to null,
Ergo:
Gson is handling a null referenced object,

Related

jackson.databind.ObjectMapper upperCasing json Array and Object names

I am trying to use JMSTemplate to publish a JSON message to a topic. This code already existed in one application and I was simply copying it to another as we are trying to consolidate two applications into one. I have found that the code is now sending JSON messages that have the first letter capitalized for the JSONArray and JSONObject field names.
I was using JMS template with a message converter that takes in an object mapper to convert from a POJO to a JSON. The only real difference in my new code is that I am using a newer version of spring boot. I know this would update all of the jackson dependencies so maybe that is why this change has occurred. I ended up trying to set the naming strategy on my object mapper but this doesn't seem to work. I originally did it in my bean definition but in order to see if it was actually working I tried it before I did a convertAndSend, and it did not work. I was still getting uppercase JSON Object and Array names.
public void sendMessage(Object responseToSend) {
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);// does not seem to make a difference
try {
System.out.println(objectMapper.writeValueAsString(responseToSend));//prints array and object names with the first letter capitolized
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
jmsTemplatePublish.convertAndSend("REDACTED",
responseToSend);
}
So, For example, my new application is sending something like.
"Quote":[{"QuoteInformation":{"Inputs":{"exampleField":false,"ExampleWritten":{"dwelling":true}}
where before it was like this
"quote":[{"quoteInformation":{"inputs":{"exampleField":false,"exampleWritten":{"dwelling":true}}
#Kachopsticks did you tried that PropertyNamingStrategy.LOWER_CASE in objectMapper namingStrategy configs instead of using PropertyNamingStrategy.LOWER_CAMEL_CASE.
This bean was the culprit. Had to remove .modulesToInstall(JaxbAnnotationModule.class);
#SuppressWarnings("unchecked")
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return Jackson2ObjectMapperBuilder.json()
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.defaultViewInclusion(true)
.modulesToInstall(JaxbAnnotationModule.class);
}

Define POJO for changing JSON responses

I am using Java 8/Spring 5 to call a third-party API. I am using HttpClient which works well, and I have a POJO that works 99% of the time.
When I call this one service, the JSON looks like:
{"field1":"a",
"field2":"b",
"field3:"c"}
Based on different parameters it could come back as:
{"field1":"a",
"field2":{
"subfield1:"x",
"subfield2:"y",
"subfield3":"z"},
"field3:"c"}
I am using the latest FastJacksonMapper to convert from JSON string to a Java POJO, and it works in the first instance, but not in the second instance.
I know it may be common for JSON to change based on requests, but I expect JSON to be a little more consistent.
Any thoughts on how I could tweak my POJO? Any JSON annotations I can use to fix this? Or, maybe create a separate POJO so that in case one fails, the other picks up?
Thanks!
So for the same URL and http method you can get different payloads? The same key "field2" might have a string value in option A and a different object in option B? IMHO that's bad design of API.
That third-party API having any description like swagger? Such description will help you generate correct POJO with proper annotations.
If such documentation is not available try to use generators like: http://www.jsonschema2pojo.org/
private static Either<Type1Obj, Type2Obj> getPojoFromJson(String json) {
try {
// Type1Obj from json
return Either.left(obj1);
} catch (IOException e) {
try {
//Type2Obj from json
return Either.right(obj2);
} catch (IOException e1) {
//fallback
}
e.printStackTrace();
}
return null;
}

JAXB Date Marshalling Issue

I have a large web service that builds a response object, the relies on JAXB for marshalling it out to the final XML to be returned. In that object I have a number of Date fields. Some of the date fields must not have time zone information and some do (based on bizarre business rules).
In the code, which has not changed for years, for that dates that must not have TZ information we have a custom type adapter to do what we need. For all the other fields we let JAXB take care of it for us: to marshal and unmarshal the Date to include the TZ information.
For every release of new builds of the service we have a custom regression system to ensure not one byte of the response changes from known good responses. In our most recent build we have an issue that cropping up periodically where those dates where the TZ is expected, it's missing:
Old Value:2014-05-14T08:24:20.283-04:00
New Value:2014-05-14T08:24:20.283Z
And, the frustrating part, is when we run the test again for the failed request, the second time the test passes: meaning the TZ is back. Very hard to debug when the bug is not reproducible on demand.
Nowhere in the code that I can find, and in all the dependencies is the system ever changing the JVM TimeZone, or the default format (Locale.setDefault) that I can find.
Has anybody every seen this type JAXB inconsistency with a Date?
We could write another custom adapter that forces the to/from marshalling to have the TZ... But I just don't understand why now, after years of use, suddenly decided to change on us.
UPDATE
Here is the new adapter. Nothing extravigant. But, Like I said, this issue happened before the addition of this adapter. After it's addition, I've no idea if it's resolved yet... No time to test yet.
public class CustomDateTimeTZAdapter extends XmlAdapter<String, Date>{
private static DateFormat getFormatter() {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); // <---------------- NOTE: The "XXX" for Timezone is only JAVA 7+ supported!
}
#Override
public Date unmarshal(String v) throws Exception {
try {
return getFormatter().parse(v);
} catch (ParseException e) {
return null;
}
}
#Override
public String marshal(Date v) throws Exception {
if (v != null) {
return getFormatter().format(v);
} else {
return null;
}
}
}

How to handle Jackson InvalidFormatException gracefully?

I'm currently struggling with Java JSON deserialization with Jackson in the following way:
I want to process and deserialize a JSON response I get back from a webservice and convert the response into POJOs with the help of Jackson. This works fine most of the time, as long as the response I'm getting contains JSON attributes in the correct format.
As however the webservice and the delivered data is out of my control, I can not rely on the data always being in the correct format.
Let me give you an example:
In my POJO, there's a java.util.Date field, and the JSON response contains a property holding a datetime string. Jackson will try to parse the string and convert it into a Date. If the date format matches the ObjectMapper's dateformat (ObjectMapper.setDateFormat(...)), everything is fine. If the format is different, I get an InvalidFormatException.
The problem now is, the dateformat sent from the service can differ. I can get dates formatted like 2014-11-02T00:00:00Z, but I can also get dates formatted like 2014-11 (identifying just a single month instead of an entire datetime).
I know, I can write a custom deserializer which could take care of this exact case and handle datestrings with different date formats correctly. But as this would only fix issues with Dates, but not with potential other datatypes, I'm searching for a more general approach.
(What happens e.g. if I expect a Double and receive an alphanumerical string?)
What I would like is to have the possibility to ignore all cases in which an InvalidFormatException happens and define a default value (like null) to the respective POJO field.
And it would be really valuable for me, if despite an invalid dateformat being returned or any other InvalidFormatException happening, the rest of the JSON would still be deserialized into the POJO.
Is this in any way possible with Jackson?
Thank you for reading my question till the end and I would be grateful for any pointers in the right direction.
Not sure if this is best practice, I have little experience with Jackson.
You can add a DeserializationProblemHandler to the ObjectMapper to specify what happens when the deserializer encounters a weird String or weird number.
In your case you could set the handler such that when encountering an unrecognized format, instead of throwing an InvalidFormatException, it just returns null:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleWeirdStringValue(DeserializationContext ctxt, Class<?> targetType, String valueToConvert, String failureMsg) throws IOException {
return null;
}
#Override
public Object handleWeirdNumberValue(DeserializationContext ctxt, Class<?> targetType, Number valueToConvert, String failureMsg) throws IOException {
return null;
}
});

Jackson throws JsonMappingException on deserialize; demands single-String constructor?

Another question, but it relates to this one:
Deserializing JSON with Jackson - Why JsonMappingException "No suitable constructor"?
This time I am getting a different error, namely that the Jackson deserializer complains that I do not have a "single-String constructor/factory method" in my class ProtocolContainer.
However, if I add a single-String constructor, like this:
public ProtocolContainer(String json) {}
the exception does indeed disappear, but the ProtocolContainer that I expected to be there is all "empty", i.e. all its properties are in their initial state, and not populated according to the JSON-string.
Why is that?
I'm pretty sure you shouldn't need a single-String constructor, and if you do that you should not have to populate the properties in that constructor, right?
=)
Oh, so once again I found out the answer AFTER I posted this question (even though I tried a lot of things before posting).
What I did to solve this was to use the #JsonCreator annotation. I simply annotated my static Create method, like this:
#JsonCreator
public static ProtocolContainer Create(String jsonString)
{
ProtocolContainer pc = null;
try {
pc = mapper.readValue(jsonString, ProtocolContainer.class);
} catch (JsonParseException|JsonMappingException|IOException e) {
// handle
}
return pc;
}
And then problem solved.
The exception suggests that the JSON value you have is a String, something like:
{ "protocol" : "http" }
or perhaps "double-quoted JSON":
"\"{\"property\":\"value\"}\"
when trying to bind like:
ProtocolContainer p = mapper.readValue(json, ProtocolContainer.class);
in which case Jackson has no properties to map, just a String. And in that case it does indeed require either a custom deserializer, or a creator method. Creator methods are either single-string-argument constructors, or single-string argument static methods: the difference being that only constructors can be auto-detected (this is just a practical short-cut as there can only be one such constructor, but multiple static methods).
Your solution does indeed work, just thought I'd give some background as to what is happening.
Reading through it second time it seems more likely you have double-quoted stuff (JSON in JSON): another thing to consider is to get plain JSON, if possible. But maybe that's hard to do.
I had the same problem. For me, the solution was to switch from passing a String to the convertValue method, to an InputStream to the readValue method:
// Instead of this:
String jsonString = "..."
ProtocolContainer pc = mapper.convertValue(jsonString, ProtocolContainer.class);
// ... do this:
String jsonString = "..."
InputStream is = new StringInputStream(jsonString);
ProtocolContainer pc = mapper.readValue(is, ProtocolContainer.class);
It seems that you are sending to the server a string instead of an object.
Instead of sending the string to be parsed on the server side, you can do it easier just by sending JSON.parse(stringObject), and Jackson will deserialize it normally as expected.

Categories