我正在使用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中看不到这种方法。
也许还有其他方法可以访问默认的日期解析行为?或者也许是我没有考虑过的完全不同的想法。
答案 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日,除非您有一些基本假设?