Fasterxml ObjectMapper: Date parsing - Unexpected character ('-' (code 45))

Yatendra picture Yatendra · Sep 13, 2015 · Viewed 9.8k times · Source

Json:

{name:"abc",TxnDateUTC:2015-09-07T21:11:19Z}

Java Code:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readValue( jsonString, Message.class );

Exception:

Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('-' (code 45)): was expecting comma to separate OBJECT entries
at [Source: java.io.StringReader@1aa7ecca; line: 1, column: 113]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1378)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:599)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:520)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:599)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:301)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2796)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1942)

I understand that since the date value is unquoted, objectMapper is throwing exception because it doesn't know how to parse an unquoted value. But since I don't have control on the json format, how can I parse this json?

The same code works fine when I quote the date value in the json string.

Answer

Nikola Yovchev picture Nikola Yovchev · Sep 13, 2015

You should maybe read this, which your question is almost an exact duplicate of (read the accepted answer):

ALLOW_UNQUOTED_FIELD_NAMES in jackon JSON library

So, in short, the allowUnquoted behaves rather erratically and in this case the - character does throw it off. So, your json should become from this:

{name:"abc",TxnDateUTC:2015-09-07T21:11:19Z}

to this:

{name:"abc",TxnDateUTC:"2015-09-07T21:11:19Z"}

And to be even stricter, it should be:

{"name":"abc","TxnDateUTC":"2015-09-07T21:11:19Z"}

Edit: Also, in the future, you should post your Message code if you expect us to help you. And since you don't have an influence on the json (which this ain't), you can write a custom parser

Editx2: Since I am a good guy, I also implemented a (rather error-prone) custom parser for you, despite you downvoting my answer. You just annotate your Message class like that:

@JsonDeserialize(using = MessageDeserializer.class)
public class Message {

And have a Deserializer like that (I assume you can convert a String to a DateTime or whatever Date you are using if you don't like the fact that both name and Txblabla are Strings and throw some exceptions in case the patterns don't match so such code is not included here):

public class MessageDeserializer extends JsonDeserializer<Message> {
    private final Pattern NAME_PATTERN = Pattern.compile(".*?(\"?)name(\"?)[\\s]*:[\\s]*(\".*\").*?");
    private final Pattern DATE_PATTERN = Pattern.compile(".*?TxnDateUTC:\"?([A-Za-z0-9\\-:]{20}).*?");

    @Override
    public Message deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        JsonLocation currentLocation = jp.getCurrentLocation();
        String jsonString = (String) currentLocation.getSourceRef();
        Matcher nameMatcher = NAME_PATTERN.matcher(jsonString);
        String name = "";
        if ( nameMatcher.matches() && nameMatcher.groupCount() == 3 ){
            name = nameMatcher.group(3);
        }

        Matcher dateMatcher = DATE_PATTERN.matcher(jsonString);
        String date = "";
        if ( dateMatcher.matches() && dateMatcher.groupCount() == 1 ){
            date = dateMatcher.group(1);
        }
        return new Message(name, date);
    }
}