[英]Same type serialization based on field name in GSON without annotations
给定一个我无法修改的 class
class ThirdPartyDTO
{
Instant foo;
Instant bar;
// many more fields.
}
我有 class 的 JSON 表示,它使用两种不同的模式来表示 foo 和 bar。
如果字段名称为 foo,则使用此模式,如果字段名称为 bar,则使用其他模式。
如何使用 gson 做到这一点而不在每个字段名称上添加(因为我不能)注释?
谢谢。
因此,正如我在上面的评论中提到的,Gson 类型的适配器无法访问它们序列化或反序列化的对象的完整上下文。 例如,单个类型(层次结构)的类型适配器并不真正知道它可能应用于哪个字段(这就是帖子中的问题)。 为了对不同的字段应用不同的类型适配器,可以使用JsonSerializer
和JsonDeserializer
(因此每个字段都必须手动处理,这是一项繁琐的工作)。 这里的另一个坏事是,应该像这样处理 DTO 的ReflectiveTypeAdapterFactory
不能直接扩展,而只能通过同样有限的GsonBuilder
接口进行扩展。
但是,可以实现使用以下算法的解决方法:
ReflectiveTypeAdapterFactory
);null
(other other defaults in case of primitives), the post-deserializer type adapter asks injected strategies to deserialize each special field that was previously被排除策略跳过,因此ReflectiveTypeAdapterFactory
。这就是诀窍。
interface IPostPatchFactory {
@Nonnull
TypeAdapterFactory createTypeAdapterFactory();
@Nonnull
ExclusionStrategy createExclusionStrategy();
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
final class PostPatchFactory
implements IPostPatchFactory {
private final Predicate<? super FieldDatum> isFieldPostPatched;
private final Predicate<? super Class<?>> isClassPostPatched;
private final Iterable<FieldPatch<?>> fieldPatches;
static IPostPatchFactory create(final Collection<FieldPatch<?>> fieldPatches) {
final Collection<FieldPatch<?>> fieldPatchesCopy = new ArrayList<>(fieldPatches);
final Collection<Field> postPatchedFields = fieldPatches.stream()
.map(FieldPatch::getField)
.collect(Collectors.toList());
final Collection<FieldDatum> postPatchedFieldAttributes = postPatchedFields.stream()
.map(FieldDatum::from)
.collect(Collectors.toList());
final Collection<? super Class<?>> isClassPostPatched = postPatchedFieldAttributes.stream()
.map(fieldDatum -> fieldDatum.declaringClass)
.collect(Collectors.toList());
return new PostPatchFactory(postPatchedFieldAttributes::contains, isClassPostPatched::contains, fieldPatchesCopy);
}
@Nonnull
@Override
public TypeAdapterFactory createTypeAdapterFactory() {
return new PostPatchTypeAdapterFactory(isClassPostPatched, fieldPatches);
}
@Nonnull
@Override
public ExclusionStrategy createExclusionStrategy() {
return new PostPatchExclusionStrategy(isFieldPostPatched);
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
private static final class PostPatchTypeAdapterFactory
implements TypeAdapterFactory {
private final Predicate<? super Class<?>> isClassPostPatched;
private final Iterable<FieldPatch<?>> fieldPatches;
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawType = typeToken.getRawType();
if ( !isClassPostPatched.test(rawType) ) {
return null;
}
return new PostPatchTypeAdapter<>(gson, gson.getDelegateAdapter(this, typeToken), fieldPatches)
.nullSafe();
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
private static final class PostPatchTypeAdapter<T>
extends TypeAdapter<T> {
private final Gson gson;
private final TypeAdapter<T> delegateTypeAdapter;
private final Iterable<FieldPatch<?>> fieldPatches;
@Override
public void write(final JsonWriter out, final T value) {
throw new UnsupportedOperationException("TODO");
}
@Override
public T read(final JsonReader in) {
final JsonElement bufferedJsonElement = JsonParser.parseReader(in);
final T value = delegateTypeAdapter.fromJsonTree(bufferedJsonElement);
for ( final FieldPatch<?> fieldPatch : fieldPatches ) {
final Field field = fieldPatch.getField();
final BiFunction<? super Gson, ? super JsonElement, ?> deserialize = fieldPatch.getDeserialize();
final Object fieldValue = deserialize.apply(gson, bufferedJsonElement);
try {
field.set(value, fieldValue);
} catch ( final IllegalAccessException ex ) {
throw new RuntimeException(ex);
}
}
return value;
}
}
}
private static final class PostPatchExclusionStrategy
implements ExclusionStrategy {
private final Predicate<? super FieldDatum> isFieldPostPatched;
private PostPatchExclusionStrategy(final Predicate<? super FieldDatum> isFieldPostPatched) {
this.isFieldPostPatched = isFieldPostPatched;
}
@Override
public boolean shouldSkipField(final FieldAttributes fieldAttributes) {
return isFieldPostPatched.test(FieldDatum.from(fieldAttributes));
}
@Override
public boolean shouldSkipClass(final Class<?> clazz) {
return false;
}
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
private static final class FieldDatum {
private final Class<?> declaringClass;
private final String name;
private static FieldDatum from(final Member member) {
return new FieldDatum(member.getDeclaringClass(), member.getName());
}
private static FieldDatum from(final FieldAttributes fieldAttributes) {
return new FieldDatum(fieldAttributes.getDeclaringClass(), fieldAttributes.getName());
}
}
}
@AllArgsConstructor(staticName = "of")
@Getter
final class FieldPatch<T> {
private final Field field;
private final BiFunction<? super Gson, ? super JsonElement, ? extends T> deserialize;
}
单元测试:
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@EqualsAndHashCode
@ToString
final class ThirdPartyDTO {
private final Instant foo;
private final Instant bar;
}
public final class PostPatchFactoryTest {
private static final Collection<FieldPatch<?>> fieldPatches;
static {
try {
final Field thirdPartyDtoFooField = ThirdPartyDTO.class.getDeclaredField("foo");
thirdPartyDtoFooField.setAccessible(true);
final Field thirdPartyDtoBarField = ThirdPartyDTO.class.getDeclaredField("bar");
thirdPartyDtoBarField.setAccessible(true);
fieldPatches = ImmutableList.<FieldPatch<?>>builder()
.add(FieldPatch.of(thirdPartyDtoFooField, (gson, jsonElement) -> {
final String rawValue = jsonElement.getAsJsonObject()
.get("foo")
.getAsString();
return Instant.parse(rawValue);
}))
.add(FieldPatch.of(thirdPartyDtoBarField, (gson, jsonElement) -> {
final String rawValue = new StringBuilder(jsonElement.getAsJsonObject()
.get("bar")
.getAsString()
)
.reverse()
.toString();
return Instant.parse(rawValue);
}))
.build();
} catch ( final NoSuchFieldException ex ) {
throw new AssertionError(ex);
}
}
private static final IPostPatchFactory unit = PostPatchFactory.create(fieldPatches);
private static final Gson gson = new GsonBuilder()
.disableInnerClassSerialization()
.disableHtmlEscaping()
.addDeserializationExclusionStrategy(unit.createExclusionStrategy())
.registerTypeAdapterFactory(unit.createTypeAdapterFactory())
.create();
@Test
public void test()
throws IOException {
final ThirdPartyDTO expected = new ThirdPartyDTO(Instant.ofEpochSecond(0), Instant.ofEpochSecond(0));
try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(PostPatchFactoryTest.class.getResourceAsStream("input.json"))) ) {
final ThirdPartyDTO actual = gson.fromJson(jsonReader, ThirdPartyDTO.class);
Assertions.assertEquals(expected, actual);
}
}
}
{
"foo": "1970-01-01T00:00:00Z",
"bar": "Z00:00:00T10-10-0791"
}
(为简单起见,该条只是一个反转的字符串,使其对于bar
格式模式变得模糊,但使测试更加健壮)
请注意,这种方法是通用的(并且可能适合Instant
以外的任何其他类型),当反序列化包含特殊字段的类时,需要 JSON 树在 memory 中缓冲(内置JsonSerializer
和JsonDeserializer
做同样的事情所以谁在乎?)失去对@SerializedName
、 @JsonAdapter
等的一些特殊支持。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.