我有一堆DTOs
,其结构如下:
public class Foobar {
private String name;
private Timestamp time1;
private Timestamp time2;
private int value;
}
我需要将Timestamps
序列化为两个单独的值(一次调用.toString()
,并根据ISO标准格式化一次),以便向后兼容旧版本从现在开始,API也支持一种不错的时间格式。
因此,Foobar的JSON
输出应如下所示:
{
"name":"<some name>",
"time1":"<some non standard time>",
"iso_time1":"<ISO formatted time>",
"time2":"<some non standard time>",
"iso_time2":"<ISO formatted time>",
"value":<some number>
}
由于现有代码,我被限制在Gson。
是否可以通用方式执行此操作,这对我的所有DTOs
都有效,而不会更改DTOs
?
我希望避免为每个现有的DTO写一个TypeAdapter/Serializer/new DTO
。
TypeAdapter
我已经尝试通过TypeAdapter
和TypeAdapterFactory
来完成,但我需要类的字段名称才能区分这两个时间戳。
write(...)
的{{1}}方法说明了我遇到的问题(TypeAdapter
):
T extends Timestamp
这里的问题是,我没有找到任何方法来获取字段名称。我尝试使用@Override
public void write(final JsonWriter out, final T value) throws IOException {
out.value(value.toString());
out.name(TIMESTAMP_ISO_PREFIX + fieldName).value(toISOFormat(value));
}
来获取它,但工厂也不知道字段名称。
JsonSerializer
我也尝试通过TypeAdapterFactory
来做,但是不可能返回两个JSON元素并返回一个JsonObject会破坏现有的API。
答案 0 :(得分:2)
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));
}
}
一些注意事项:
Timestamp
属性。它依赖于getter方法的存在。如果您没有吸气剂,您将不得不使用其他方法来阅读您的时间戳属性。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符合命名模式或实现通用接口/具有共同注释,则可以进一步限制注册的内容。
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());
}
}
答案 1 :(得分:0)
一个简单,简短且由DTO驱动的解决方案是为同一个字段创建第二个具有不同名称的getter / setter。
public class SerializationTest {
private String foo;
public String getFoo() { return foo; }
// getter for json serialization
public String getBar() { return foo; }
}
您可能必须修改序列化设置才能生效,如下所示:
objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY);
objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY);
另请注意,此方法存在潜在的缺陷,例如在反序列化时 - 两个setter可能会将相同的变量设置为不同的值。