Is there a way to peek the value in GSON or rewind nextString?

I'm using GSON, Java, Kotlin and Android.

Due to factors outside my control, backend is sending us JSON date values in several formats that GSON can't handle by default. I thought I'd make a custom TypeAdapter to handle it.

I still need to handle many standard formatted Dates as well.

I currently have this:

private class DateTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = (TypeAdapter<T>)gson.getDelegateAdapter(this, type);
        if (!Date.class.isAssignableFrom(type.getRawType())) return null;
        //TODO: maybe avoid creating a new object on each method call?
        return (TypeAdapter<T>)new TypeAdapter<Date>() {
            public Date read(JsonReader reader) throws IOException {
                if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull();
                    return null;
                }
                String dateString = reader.nextString();

                try {
                    if (yearMonthDayPattern.matcher(dateString).matches()) {
                        return yearMonthDayFormat.parse(dateString);
                    } else if (dateHourMinutePattern.matcher(dateString).matches()) {
                        return dateHourMinuteFormat.parse(dateString);
                    } else {TypeToken<Date>() {});
                        return (Date)delegate.read(reader);
                    }
                } catch (ParseException e) {
                    Timber.wtf(e);
                    return null;
                }
            }

            @Override
            public void write(JsonWriter out, Date value) throws IOException {
                delegate.write(out, (T)value);
            }
        };
    }
}

Here's the problem I'm running into: I must consume the value (using nextString() in order to check and see if it is one of the weird formats, and if so, which one. But when I pass regular Date values to the delegate for default handling, the delegate assumes the value has not been called yet and tries to advance the stream, resulting in an error. How can I work around this?

If there was a way to observe the value without consuming it, I could call that, check that value in the first two if statements, and then call nextString() if either of them passes to manually advance the stream. But peek() seems to only return a type, not a value.

If there was a way to "rewind" the stream, I could call nextString() exactly as I am doing now, then rewind it by one before passing the call to the delegate. But I don't see such a method in the JsonReader API.

Or perhaps there is a different way to access the default Date parsing behavior? Or perhaps some completely different idea I have not considered.

I can't find a way to read the next value without consuming it, or to rewind the stream to un-consume it. I also can't find a different way to work with the delegate to access the default behavior. I did find the class that does the default date parsing: https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java

Unfortunately, although its constructors allow for passing in ONE extra Date format, you can't pass in two, which is what I need. The class is final and the method I need is private. I even tried copying and pasting the class into my code so I could modify it to get the functionality I need, which would have been less than ideal--but the GSON library is set up so some of the classes DefaultDateTypeAdapter requires cannot be accessed from other apps, and I didn't want to copy and paste multiple files into my app...

Fortunately, I then noticed that the deserializeToDate() method will actually take the original date String and set it as the message of the Exception it throws when it cannot parse a value. That means that I can call the default parser, and if it fails, pull that date string out and parse it however I want.

This is what I ended up with, and it seems to work fine:

private class DateTypeAdapterFactory implements TypeAdapterFactory {
    private DateFormat yearMonthDayFormat = DateFormatters.getDateFormatInUserTimeZone(FORMAT_TYPE_YYYY_MM_DD);
    private DateFormat dateHourMinuteFormat = DateFormatters.getDateFormatInUserTimeZone(DateFormatters.FORMAT_TYPE_ISO8601_NO_SECONDS);

    private Pattern yearMonthDayPattern = Pattern.compile("^\\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$");
    private Pattern dateHourMinutePattern = Pattern.compile(
            "[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T(2[0-3]|[01][0-9]):[0-5][0-9]");

    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        if (!Date.class.isAssignableFrom(type.getRawType())) return null;

        return (TypeAdapter<T>) new TypeAdapter<Date>() {
            public Date read(JsonReader reader) throws IOException {
                if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull();
                    return null;
                }

                try {
                    return (Date) delegate.read(reader);
                } catch (JsonSyntaxException jse) {
                    // DefaultDateTypeAdapter.deserializeToDate sets the date string as the JsonSyntaxException message,
                    // allowing us to try parsing it our own way.
                    String dateString = jse.getMessage();

                    try {
                        // If default date parsing fails, try one of the two funky new formats. Here we're using
                        // precompiled regexes for performance, but we could also try parsing and checking for
                        // ParseExceptions - that's what DefaultDateTypeAdapter does.
                        if (yearMonthDayPattern.matcher(dateString).matches()) {
                            return yearMonthDayFormat.parse(dateString);
                        } else if (dateHourMinutePattern.matcher(dateString).matches()) {
                            return dateHourMinuteFormat.parse(dateString);
                        } else {
                            throw jse;
                        }
                    } catch (ParseException pe) {
                        Timber.wtf(pe);
                        return null;
                    }
                }
            }

            @Override
            public void write(JsonWriter out, Date value) throws IOException {
                delegate.write(out, (T)value);
            }
        };
    }
}

Recently Active 'gson' Questions - Page 38, Gson is Google's open-source library for serializing and deserializing Java objects to/from Is there a way to peek the value in GSON or rewind nextString? 1) You have to create custom JsonDeserializer and not JsonSerializer like in your question.. 2) I don't think this behavior comes from Double deserializer. it is more like json object/map problem

You don't need any explicit reference to DefaultDateTypeAdapter since delegates are still fine. Also, the whole and the single point of streamed reading is reading it once without rewinding (why do you need to pay for buffering if you don't need it? what is the depth of rewinding? what if buffered JSON tokens are too large to keep in memory especially for huge objects and strings?). Once you've consumed a value, you can choose what to do with it: discard it away, print it out, keep it, store it, whatever it. Totally up to you. Now, since you have it consumed, what if you convert the value to a JSON tree representation? In general, it's a well-formed JSON value that does not need to be re-read, and rewinding is merely unapplicable here: this is just a representation in memory you can traverse in arbitrary order up to your needs. Having that said, take a look at fromJsonTree. It can consume trees converting them to streams that can be consumed by underlying type adapters (and they don't even know they are consuming not streams, but trees). So, the only thing you have to change

return (Date)delegate.read(reader);

to

retutn (Date)delegate.fromJsonTree(new JsonPrimitive(dateString));

That should work.

(lsh)

com.google.gson.stream.JsonReader.peek java code examples , JsonToken#BOOLEAN value of the next token, consuming it. setLenient. Configure this parser to be liberal in what it accepts. By default, this parser is strict and  Gson is the main class for using Gson library. There are two basic ways to create Gson: new Gson() new GsonBuilder().create() GsonBuilder can be used to build Gson with various configuration settings. Java Gson toJson. The toJson() method serializes the specified object into its equivalent JSON representation.

How about just simply creating and registering JsonDeserializer<Date> ? Something like:

public class DateDeserializer implements JsonDeserializer<Date> {
    // for default date parsing
    private final Gson innerGgson = new Gson();

    @Override
    public Date deserialize(JsonElement jsonElement, Type type, 
                            JsonDeserializationContext jsonDeserializationContext)
            throws JsonParseException {

        try {
            return innerGgson.fromJson(jsonElement, Date.class);
        } catch (Exception e) {
            // default parsing failed, try other formats
        }

        return parseDate(jsonElement.getAsString());
    }

    // all the possible date formats listed here 
    private static final DateFormat[] DATE_FORMATS = new DateFormat[] {
            new SimpleDateFormat("MM/dd/yyyy"),
            new SimpleDateFormat("ddMMyyyy") };

    // as an example, in this method, first try to instantiate all the 'normal' 
    // dateformats then something like it has now:
    // trying all the date formats, except ticks that then can be tried also
    // separately
    private Date parseDate(String dateString) {
        for (DateFormat df : DATE_FORMATS) {
            try {
                return df.parse(dateString);
            } catch (ParseException e) {
                // just try the next
            }
        }
        // finally, if not parsed return null or throw something
        return null;
    }

}

registering:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Date.class, new DateDeserializer())
    .create();

I think you still realize there just is no solution if the formats you obtain can be interpreted to more than one Dates, meaning that if you get 01/02/2018 is it Jan 2nd 2018 or Feb 1st 2018 unless you have some base assumptions?

com.google.gson.stream.JsonReader.skipValue java code , Best Java code snippets using com.google.gson.stream.JsonReader.skipValue How to dynamically handle json response array/object using Gson. while (​reader. nextName(); Object value = null; switch(reader.peek()) { case STRING: value = reader.nextString(); break; case BOOLEAN: value = reader.nextBoolean​();  Since #667 JsonWriter#jsonValue() allows emitting a raw json blob when generating json. I&#39;d like the equivalent for parsing json. In my case, I have large/complex json sub-trees that I would li

JsonReader (Gson 2.3 API), Then create a while loop that assigns values to local variables based on their name. This loop "id": 912345678901, "text": "How do I read a JSON stream in Java?", "geo": null nextString(); } else if (name.equals("geo") && reader.peek() != GSON has two separate APIs (that can be combined): one is used for serialization and deserialization, and the other for streaming. If you want to process streams of JSON without memory overhead or using dynamic structures (rather than static POJOs) you can do something like:

Check if element exists, how to check if element is not present in selenium javascript the last item in an array; 6498 | Is there a way to peek the value in GSON or rewind nextString? Is there a way to tell Gson, "if you deserialize the field runtime of the Type Foo and there is a NumberFormatException, just return the default value 0"? My workaround is to use a String as the Type of the runtime field instead of int , but maybe there is a better way to handle such errors.

How to Reuse Java Streams, Extract the third value corresponding to a temperature in Celsius. Thus, when the resulting csv is available (i.e. Stream<String> ), it will  Gson has a new method in JsonReader class that gives the path of the element where the JsonReader cursor is. To demonstrate JsonPath we use the same example as above, i.e. the music archive. We print the json path for each string element.

Comments
  • If I were you, I would try to generate GSON POJOs using yous JSON as input, maybe it will be able to generate the proper wrappers and you don't have to cope with TypeAdapters. If it is not able to do that, then it would be good to see at least the problemmatic part of your JSON (please update your question with it if possible)
  • The problem is that this does not handle default date parsing, only date parsing in the two new formats. As I explained in my question and answer, one of the trickiest bits was handling both the strange new formats, and the normal date formats that most of the app uses.
  • That is not a problem,really, this just was not complete code. See my update in a minute.