有没有一种方法可以查看GSON中的值或倒带nextString?

时间:2018-09-08 13:59:20

标签: java gson

我正在使用GSON,Java,Kotlin和Android。

由于我无法控制的因素,后端向我们发送了GSON默认无法处理的几种格式的JSON日期值。我以为我会自定义TypeAdapter来处理它。

我仍然需要处理许多标准格式的日期。

我目前有这个:

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);
            }
        };
    }
}

这是我遇到的问题:我必须使用该值(使用nextString()来检查它是否是一种奇怪的格式,如果是,是哪种格式。但是当我通过时将delegate的常规Date值设置为delegate以进行默认处理,nextString()假设尚未调用该值,并尝试推进流,从而导致错误。如何解决此问题?

如果有一种方法可以观察到该值而不消耗它,那么我可以调用它,在前两个if语句中检查该值,然后调用peek(),如果它们中的任何一个通过以手动方式推进流。但是nextString()似乎只返回类型,而不返回值。

如果有一种方法可以“倒带”流,则可以像现在一样完全调用JsonReader,然后将其倒带一个,然后再将调用传递给委托。但是我在main() {P0=0x00; P0=0xFF; while(1); } API中看不到这种方法。

也许还有其他方法可以访问默认的日期解析行为?或者也许是我没有考虑过的完全不同的想法。

3 个答案:

答案 0 :(得分:0)

我无法找到一种方法来读取下一个值而不消耗它,或者倒回流以使其不消费。我也找不到与委托一起使用以访问默认行为的其他方法。我确实找到了执行默认日期解析的类: https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java

不幸的是,尽管其构造函数允许传递一种额外的Date格式,但您不能传递两种,这正是我所需要的。该类是final,我需要的方法是private。我什至尝试将类复制并粘贴到我的代码中,以便我可以对其进行修改以获取所需的功能,这可能不理想—但是GSON库已建立,因此无法从中访问DefaultDateTypeAdapter所需的某些类其他应用,而我不想将多个文件复制并粘贴到我的应用中...

幸运的是,我然后注意到deserializeToDate()方法实际上将采用原始日期String,并将其设置为无法解析值时抛出的Exception的消息。这意味着我可以调用默认的解析器,如果失败,请拉出该日期字符串并根据需要进行解析。

这就是我最终得到的结果,它似乎可以正常工作:

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);
            }
        };
    }
}

答案 1 :(得分:0)

您不需要任何对DefaultDateTypeAdapter的显式引用,因为委托仍然可以。另外,流式阅读的整体和单点是一次一次而不进行倒带(为什么不需要,为什么需要为缓冲支付费用?倒带的深度是多少?如果缓冲的JSON令牌太大而无法保存在内存中,尤其是对于大型对象和字符串?)。消耗完一个值后,您可以选择处理该值:丢弃它,打印出来,保留它,存储它,不管它是什么。完全由您决定。现在,由于已经消耗掉了,如果将值转换为JSON树表示怎么办?通常,它是一个格式正确的JSON值,不需要重新读取,并且倒退在这里不适用:这只是内存中的一种表示形式,您可以根据需要按任意顺序遍历。话虽如此,看看fromJsonTree。它可以使用树来将它们转换为可由基础类型适配器使用的流(并且它们甚至不知道不是在消耗流,而是在消耗树)。因此,您唯一需要更改的地方

return (Date)delegate.read(reader);

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

应该可以。

(lsh)

答案 2 :(得分:0)

仅创建和注册JsonDeserializer<Date>怎么样?像这样:

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;
    }

}

注册:

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

我认为您仍然意识到,如果可以将所获得的格式解释为多个Date,那还没有解决方案,这意味着如果您获得01/02/2018,就可以了吗? 2018年1月2日 2018年2月1日,除非您有一些基本假设?