[英]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.