Reputation: 911
I'm reading JSON data from an API using Jackson and most of the time I get an array of objects that are all fairly standard in their implementation. The only issue is that sometimes dates will be in the format "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" and sometimes in the format "yyyy-MM-dd", so the JSON looks like this:
[
{
'A':'Foo'
'B':'2016-11-03T12:35:23.032Z'
'C':'7'
},
{
'A':'Bar'
'B':'2016-11-06'
'C':'4'
},
{
'A':'Bla'
'B':'2016-11-07T14:42:18.832Z'
'C':'23'
},
{
'A':'Blo'
'B':'2016-11-07T15:12:23.439Z'
'C':'9'
}
]
Every time I get to that second date I get a parser error because it's not in the same format. I tried writing a class that will use a second DateFormat if the first one fails, but now I just get a NullPointerException.
public class BackupDateFormat extends DateFormat {
private final LinkedList<DateFormat> formats;
public BackupDateFormat(DateFormat... dfs) {
formats = new LinkedList<>(Arrays.asList(dfs));
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
return formats.getFirst().format(date, toAppendTo, fieldPosition);
}
@Override
public Date parse(String source, ParsePosition pos) {
return formats.getFirst().parse(source, pos);
}
@Override
public Date parse(String source) throws ParseException {
ParseException exception = null;
for (DateFormat df : formats) {
try {
return df.parse(source);
}
catch (ParseException pe) {
exception = pe;
}
}
throw exception;
}
}
His is the error I'm getting:
com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.company.api.API$Result["result"]->java.util.ArrayList[0]->com.company.models.othercompany.Record["dateTime"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:391)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:351)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1597)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:278)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:294)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:266)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:485)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:108)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3836)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2860)
at com.mentoredata.api.API.get(API.java:56)
This is the code at line 56:
return mapper.<List<T>>readValue(new URL(url), mapper.getTypeFactory().constructCollectionType(List.class, type));
Does anyone know how I can either fix my current code to not get a null pointer or use two dateformats in Jackson?
Upvotes: 0
Views: 142
Reputation: 446
I'm having trouble telling exactly what your problem is, but I was able to get things working with the code you have. First, I created a POJO representing the object you are trying to deserialize. I'm kind of assuming you already have an equivalent, but this was mine:
class Obj {
String A;
Date B;
Integer C;
/* with getters/setters */
}
Then, I created a custom deserializer with the object mapper. The class is not too difficult to implement. I used your BackupDateFormat inside of the deserializer:
class ObjDeserializer extends JsonDeserializer<Obj> {
final BackupDateFormat backupDateFormat;
public ObjDeserializer() {
backupDateFormat = new BackupDateFormat(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
new SimpleDateFormat("yyyy-MM-dd"));
}
@Override
public Obj deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// create a POJO and populate with the fields from the JSON Object
Obj obj = new Obj();
JsonNode root = p.readValueAsTree();
obj.setA(root.get("A").asText(""));
obj.setC(root.get("C").asInt(0));
try {
obj.setB(backupDateFormat.parse(root.get("B").asText()));
} catch (ParseException e) {
throw new IOException("Could not parse date as expected.");
}
return obj;
}
}
After that, register your serializer with the ObjectMapper:
SimpleModule dateDeserializerModule = new SimpleModule();
// associate the custom deserializer with your POJO
dateDeserializerModule.addDeserializer(Obj.class, new ObjDeserializer());
mapper.registerModule(dateDeserializerModule);
Finally, you can see that the dates were appropriately parsed from this snippet and its output:
List<Obj> result = mapper.readValue(input.getBytes(), mapper.getTypeFactory().constructCollectionType(List.class, Obj.class));
result.forEach(x->System.out.println(x.getB().toString()));
Thu Nov 03 12:35:23 EDT 2016
Sun Nov 06 00:00:00 EDT 2016
Mon Nov 07 14:42:18 EST 2016
Mon Nov 07 15:12:23 EST 2016
If you find something built into Jackson that will do this, I encourage you to use it, but sometimes you end up needing the extra level of customization that this method provides. Hope it helps.
Check out the question below too. It's similar, but doesn't deal with multiple formats. The accepted answer uses the custom deserializer as well.
References:
Getting started with deserializers
Upvotes: 1