final class StrictEnumTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory allStrictEnumTypeAdapterFactory = new StrictEnumTypeAdapterFactory(enumClass -> true);
private final Predicate<? super Class<? extends Enum<?>>> isStrictEnumClass;
private StrictEnumTypeAdapterFactory(final Predicate<? super Class<? extends Enum<?>>> isStrictEnumClass) {
this.isStrictEnumClass = isStrictEnumClass;
}
static TypeAdapterFactory get(final Predicate<? super Class<? extends Enum<?>>> isStrictEnumClass) {
return new StrictEnumTypeAdapterFactory(isStrictEnumClass);
}
static TypeAdapterFactory get() {
return allStrictEnumTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawType = typeToken.getRawType();
// Skip non-enums
if ( !Enum.class.isAssignableFrom(rawType) ) {
return null;
}
// Check if the enum is supported by the "strict" policy
@SuppressWarnings("unchecked")
final Class<? extends Enum<?>> enumRawType = (Class<? extends Enum<?>>) rawType;
if ( !isStrictEnumClass.test(enumRawType) ) {
return null;
}
// Trivial rawtypes/unchecked casts
@SuppressWarnings({ "rawtypes", "unchecked" })
final TypeAdapter<? extends Enum<?>> strictEnumTypeAdapter = StrictEnumTypeAdapter.get((Class) enumRawType);
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) strictEnumTypeAdapter;
return castTypeAdapter;
}
private static final class StrictEnumTypeAdapter<E extends Enum<E>>
extends TypeAdapter<E> {
private final Class<E> enumClass;
private final Map<String, E> nameToEnumConstant;
private final Map<E, String> enumConstantToName;
private StrictEnumTypeAdapter(final Class<E> enumClass, final Map<String, E> nameToEnumConstant, final Map<E, String> enumConstantToName) {
this.enumClass = enumClass;
this.nameToEnumConstant = nameToEnumConstant;
this.enumConstantToName = enumConstantToName;
}
private static <E extends Enum<E>> TypeAdapter<E> get(final Class<E> enumClass) {
final Map<String, E> nameToEnumConstant = new HashMap<>();
final Map<E, String> enumConstantToName = new HashMap<>();
final Map<String, E> enumNameToEnumConstant = Stream.of(enumClass.getEnumConstants())
.collect(Collectors.toMap(Enum::name, Function.identity()));
Stream.of(enumClass.getFields())
// It can be either a simple enum constant, or an enum constant that overrides
.filter(field -> enumClass.isAssignableFrom(field.getType()))
.forEach(field -> {
final E enumConstant = enumNameToEnumConstant.get(field.getName());
// For compatibility with the original type adapter, we have to respect the @SeriaizedName annotation
final SerializedName serializedName = field.getAnnotation(SerializedName.class);
if ( serializedName == null ) {
nameToEnumConstant.put(field.getName(), enumConstant);
enumConstantToName.put(enumConstant, field.getName());
} else {
nameToEnumConstant.put(serializedName.value(), enumConstant);
enumConstantToName.put(enumConstant, serializedName.value());
for ( final String alternate : serializedName.alternate() ) {
nameToEnumConstant.put(alternate, enumConstant);
}
}
});
return new StrictEnumTypeAdapter<>(enumClass, nameToEnumConstant, enumConstantToName)
.nullSafe(); // A convenient method to handle nulls
}
@Override
public void write(final JsonWriter out, final E value)
throws IOException {
out.value(enumConstantToName.get(value));
}
@Override
public E read(final JsonReader in)
throws IOException {
final String key = in.nextString();
// This is what the original type adapter probably misses
if ( !nameToEnumConstant.containsKey(key) ) {
throw new JsonParseException(enumClass + " does not have an enum named " + key + " at " + in);
}
return nameToEnumConstant.get(key);
}
}
}
简单测试:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(StrictEnumTypeAdapterFactory.get())
.create();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q49572505.class, "good.json") ) {
System.out.println(gson.<Status>fromJson(jsonReader, Status.class).access);
}
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q49572505.class, "bad.json") ) {
try {
gson.<Status>fromJson(jsonReader, Status.class);
throw new AssertionError();
} catch ( final JsonParseException ex ) {
System.out.println(ex.getMessage());
}
}
}
class StrictEnumTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (!rawType.isEnum()) {
return null;
}
return newStrictEnumAdapter(gson.getDelegateAdapter(this, type));
}
private <T> TypeAdapter<T> newStrictEnumAdapter(
final TypeAdapter<T> delegateAdapter) {
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegateAdapter.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
// Peek at the next value and save it for the error message
// if you don't need the offending value's actual name
String enumValue = in.nextString();
JsonReader delegateReader = new JsonReader(new StringReader('"' + enumValue + '"'));
T value = delegateAdapter.read(delegateReader);
delegateReader.close();
if (value == null) throw new IllegalStateException("Invalid enum value - " + enumValue);
return value;
}
};
}
}
3条答案
按热度按时间gk7wooem1#
从2.8.2版本开始,Gson不支持这样的用例。我相信这是值得提交给Gson开发团队的一个建议,作为一种特殊的
GsonBuilder
配置方法。您现在最多只能编写一个自定义枚举类型适配器,它几乎复制了com.google.gson.internal.bind.EnumTypeAdapter
功能,但添加了名称检查。简单测试:
输出:
读_写
类q49572505.AccessMode在JsonReader第2行第22列路径$.access中没有名为READ_XXX的枚举
wqsoz72f2#
您可以查看@Moshi。我发现它是GSON的一个合适且直接的替代品,并且它已经支持这种行为。
@Lyubomyr_Shaydarlv的解决方案是可行的,但如果你不想复制GSON的内部代码,你可以在一个自定义的
TypeAdapterFactory
中将它作为委托使用。运行适配器,如果它返回null
,您就知道该值无效。这样做的好处是它继承并更改为默认的enum
转换器。ukqbszuj3#
对于交叉引用,我想将未知值重新Map到
enum
类型中的 fallback value。如果在另一端字段有新值,但代码尚未对其进行反序列化,这将非常有用。https://github.com/google/gson/issues/608#issuecomment-1592929122
我为gson编写了这个通用的
TypeAdapterFactory
。在Gson注册:
注解要重新Map到的枚举值
现在像这样的有效载荷将具有重新Map到
UNKNOWN
的kind值。