Ответ 1
Подход 1: используйте JsonSerialiser
Вы можете создать JsonSerialiser
для своих объектов (т.е. на один уровень выше, чем Timestamp
) и использовать его для добавления дополнительных полей по мере необходимости:
/**
* Appends extra fields containing ISO formatted times for all Timestamp properties of an Object.
*/
class TimestampSerializer implements JsonSerializer<Object> {
private Gson gson = new GsonBuilder().create();
@Override
public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
JsonElement tree = gson.toJsonTree(src);
if (tree instanceof JsonObject) {
appendIsoTimestamps(src, (JsonObject) tree);
}
return tree;
}
private JsonObject appendIsoTimestamps(Object src, JsonObject object) {
try {
PropertyDescriptor[] descriptors = Introspector.getBeanInfo(src.getClass()).getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
if (descriptor.getPropertyType().equals(Timestamp.class)) {
Timestamp ts = (Timestamp) descriptor.getReadMethod().invoke(src);
object.addProperty("iso_" + descriptor.getName(), ts.toInstant().toString());
}
}
return object;
} catch (IllegalAccessException | InvocationTargetException | IntrospectionException e) {
throw new JsonIOException(e);
}
}
Пример использования:
public class GsonSerialiserTest {
public static void main(String[] args) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Foobar.class, new TimestampSerializer());
Gson gson = builder.create();
Foobar baz = new Foobar("baz", 1, new Timestamp(System.currentTimeMillis()));
System.out.println(gson.toJson(baz));
}
}
Некоторые примечания:
- В этом примере пользовательский конструктор java bean находит свойства
Timestamp
. Он опирается на наличие геттерных методов. Если у вас нет геттеров, вам придется использовать какой-либо другой метод для чтения ваших свойств метки времени. - Сериализатор делегирует другому строителю gson (он не может вызывать тот, что находится в
JsonSerializationContext
, или он в конечном итоге вызывает себя рекурсивно). Если ваша существующая сериализация зависит от другого инструментария в построителе, вам придется подключить отдельный строитель и передать его в сериализатор.
Если вы хотите сделать это для всех объектов, которые вы сериализуете, зарегистрируйте адаптер для всей иерархии Object
:
builder.registerTypeHierarchyAdapter(Object.class, typeAdapter);
Если вы хотите изменить подмножество DTO, вы можете зарегистрировать их динамически. Библиотека Reflections упрощает:
TimestampSerializer typeAdapter = new TimestampSerializer();
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false))
.setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.contextClassLoader()))
.filterInputsBy(new FilterBuilder().includePackage("com.package.dto", "com.package.other")));
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
for (Class<?> type : classes) {
builder.registerTypeAdapter(type, typeAdapter);
}
Приведенный выше пример регистрирует все в именованных пакетах. Если ваши DTO соответствуют шаблону именования или реализуют общий интерфейс/имеют общую аннотацию, вы можете еще больше ограничить регистрацию.
Подход 2: Зарегистрируйте a TypeAdapterFactory
TypeAdapters
работают на уровне читателя/писателя и требуют немного больше работы для реализации, но они дают вам больше контроля.
Регистрация TypeAdapterFactory
с помощью построителя позволит вам контролировать, какие типы редактировать. В этом примере адаптер применяется ко всем типам:
public static void main(String[] args) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapterFactory(new TypeAdapterFactory() {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Return null here if you don't want to handle the type.
// This example returns an adapter for every type.
return new TimestampAdapter<>(type);
}
});
Gson gson = builder.create();
Foobar baz = new Foobar("baz", 1);
String json = gson.toJson(baz);
System.out.println(json);
System.out.println(gson.fromJson(json, Foobar.class));
}
И адаптер...
class TimestampAdapter<T> extends TypeAdapter<T> {
private TypeToken<T> type;
private Gson gson = new GsonBuilder().create();
public TimestampAdapter(TypeToken<T> type) {
this.type = type;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
JsonObject object = appendIsoTimestamps(value, (JsonObject) gson.toJsonTree(value));
TypeAdapters.JSON_ELEMENT.write(out, object);
}
private JsonObject appendIsoTimestamps(T src, JsonObject tree) {
try {
PropertyDescriptor[] descriptors = Introspector.getBeanInfo(src.getClass()).getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
if (descriptor.getPropertyType().equals(Timestamp.class)) {
Timestamp ts = (Timestamp) descriptor.getReadMethod().invoke(src);
tree.addProperty("iso_" + descriptor.getName(), ts.toInstant().toString());
}
}
return tree;
} catch (IllegalAccessException | InvocationTargetException | IntrospectionException e) {
throw new JsonIOException(e);
}
}
@Override
public T read(JsonReader in) {
return gson.fromJson(in, type.getType());
}
}