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;
}
});
Related
Does anyone know if I'm able to take full JSON string,
from below objects during custom serialization or deserialization:
JsonParser jp
DeserializationContext ctxt
SeserializationContext ctxt
I'm using JACKSON on rest API.
Object source = jsonParser.getCurrentLocation.getSourceRef();
Unfortunately I'm not able to take the JSON as string because this method is giving back an object which is: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.UnCloseableInputStream
Object source = jsonParser.getCurrentLocation.getSourceRef();
It holds a reference to the original resource being read, if one available. See the documentation.
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,
I've read a bunch of different articles, comparations and tutorials that are using different JSON-Libraries for parsing (and creating) JSON into Java Objects. Anyway I think that I've got the facts right cause I've decided to use the JSON library called Jackson.
GSON is simple and robust but way to slow acording to me. So I decided to actually try this Jackson thing out but, it seems like the parsing is a little bit more confusing here than with GSON.
The data-type of the value that I want to parse is simply an Boolean.
This is what the JSON that I'm trying to parse looks like:
{"FooResult":true}
So what I actually need help with is selecting the value from the key FooResult and then parse its value into an Boolean.
This Is what I've done so far:
String json = getString(request);
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(json, Boolean.class);
But this code obviously gives me an error cause I haven't selected that it is the FooResult key that I'm interested in reading & parsing into an Boolean.
You should create a new class like this:
class MyClass {
public boolean FooResult;
}
And use this code to load the data:
MyClass myObject = mapper.readValue(json, MyClass.class);
Then you can access the value with myObject.FooResult
Ok this is lame. Even lamer when I rethink about it. The problem the whole time have been that the class of the object that you want to parse needs to be static. I've tried what Simon suggested like four or five times before I even posted this question today but the problem all time was that the class wasn't static.
So now it finally works.
static class FooClass
{
public boolean FooResult;
}
And for the parsing process.
String json = getString(request);
ObjectMapper mapper = new ObjectMapper();
FooClass fooClass = null;
try
{
fooClass = mapper.readValue(json, FooClass.class);
}
boolean result = fooClass.FooResult;
I have a class in Java that is generically typed. It is supposed to return an object of type T after receiving some json. I am using the following code to create the object:
ObjectMapper mapper = new ObjectMapper();
this.object = mapper.readValue(json, type);
This method throws a JsonMappingException, and should do so if the object isn't of the proper type. The problem I'm running into (when unit testing) is that if I pass in json of an incorrect type, as long as both objects are pojos no exception is being thrown. I am simply getting back an object of the correct type where all it's fields are null.
Why is the exception not getting thrown here? If I pass in some json with a bunch of fields that don't exist on the type of object it should be mapping to, shouldn't I get an exception from that?
You possibly have:
#JsonIgnoreProperties(ignoreUnknown = true)
set somewhere, so jackson doesn't complain about the mismatch.
How do you expect Jackson to know JSON does not represent expected type? JSON data does not have type, beyond just basic Object/Array/scalars structure. So as long as structure is compatible things work, and this is by design.
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.