4

我已经看到 Enum 的默认 TypeAdapter 不符合我的需要:

private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
    private final Map<String, T> nameToConstant = new HashMap<String, T>();
    private final Map<T, String> constantToName = new HashMap<T, String>();

    public EnumTypeAdapter(Class<T> classOfT) {
      try {
        for (T constant : classOfT.getEnumConstants()) {
          String name = constant.name();
          SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
          if (annotation != null) {
            name = annotation.value();
          }
          nameToConstant.put(name, constant);
          constantToName.put(constant, name);
        }
      } catch (NoSuchFieldException e) {
        throw new AssertionError();
      }
    }
    public T read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      return nameToConstant.get(in.nextString());
    }

    public void write(JsonWriter out, T value) throws IOException {
      out.value(value == null ? null : constantToName.get(value));
    }
  }

如果 Enum 的值为 ONE 和 TWO,当我们尝试解析 THREE 时,该值是未知的,Gson 将映射 null 而不是引发解析异常。我需要一些更快速失败的东西。

但我还需要一些东西来让我知道当前读取的字段的名称并导致解析失败。

Gson可以吗?

4

1 回答 1

7

的。

Gson 非常模块化,允许您将自己的TypeAdapterFactory用于枚举案例。您的自定义适配器将返回您自己的EnumTypeAdapter并管理所需的案例。让代码说话。

package stackoverflow.questions.q16715117;

import java.io.IOException;
import java.util.*;

import com.google.gson.*;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.*;

public class Q16715117 {

    public static void main(String[] args) {
    GsonBuilder gb = new GsonBuilder(); 
    gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);

    Container c1 = new Container();


    Gson g = gb.create();
    String s1 = "{\"colour\":\"RED\",\"number\":42}";
    c1 = g.fromJson(s1, Container.class);
    System.out.println("Result: "+ c1.toString());
    }


    public static final TypeAdapterFactory CUSTOM_ENUM_FACTORY = newEnumTypeHierarchyFactory();

    public static TypeAdapterFactory newEnumTypeHierarchyFactory() {
        return new TypeAdapterFactory() {
          @SuppressWarnings({"rawtypes", "unchecked"})
          public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            Class<? super T> rawType = typeToken.getRawType();
            if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
              return null;
            }
            if (!rawType.isEnum()) {
              rawType = rawType.getSuperclass(); // handle anonymous subclasses
            }
            return (TypeAdapter<T>) new CustomEnumTypeAdapter(rawType);
          }
        };
      }


    private static final class CustomEnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
        private final Map<String, T> nameToConstant = new HashMap<String, T>();
        private final Map<T, String> constantToName = new HashMap<T, String>();
        private Class<T> classOfT;

        public CustomEnumTypeAdapter(Class<T> classOfT) {
          this.classOfT = classOfT;
          try {
            for (T constant : classOfT.getEnumConstants()) {
              String name = constant.name();
              SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
              if (annotation != null) {
                name = annotation.value();
              }
              nameToConstant.put(name, constant);
              constantToName.put(constant, name);
            }
          } catch (NoSuchFieldException e) {
            throw new AssertionError();
          }
        }
        public T read(JsonReader in) throws IOException {
          if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
          }

          String nextString = in.nextString();
          T enumValue = nameToConstant.get(nextString);

          if (enumValue == null)
          throw new GsonEnumParsinException(nextString, classOfT.getName());

          return enumValue;
        }

        public void write(JsonWriter out, T value) throws IOException {
          out.value(value == null ? null : constantToName.get(value));
        }
      }

}

另外我声明了一个自定义运行时异常:

public class GsonEnumParsinException extends RuntimeException {

    String notFoundEnumValue;
    String enumName;
    String fieldName;

    public GsonEnumParsinException(String notFoundEnumValue, String enumName) {
      this.notFoundEnumValue = notFoundEnumValue;
      this.enumName = enumName;
    }



    @Override
    public String toString() {
    return "GsonEnumParsinException [notFoundEnumValue="
        + notFoundEnumValue + ", enumName=" + enumName + "]";
    }



    public String getNotFoundEnumValue() {
        return notFoundEnumValue;
    }

    @Override
    public String getMessage() {
    return "Cannot found " + notFoundEnumValue  + " for enum " + enumName;
    }


}

这些是我在示例中使用的类:

public enum Colour {  
    WHITE, YELLOW, BLACK;
}

public class Container {

    private Colour colour;
    private int number;

    public Colour getColour() {
    return colour;
    }

    public void setColour(Colour colour) {
    this.colour = colour;
    }

    public int getNumber() {
    return number;
    }

    public void setNumber(int number) {
    this.number = number;
    }

    @Override
    public String toString() {
    return "Container [colour=" + colour + ", number=" + number + "]";
    }

}

这给出了这个堆栈跟踪:

Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour]
    at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:77)
    at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
    at com.google.gson.Gson.fromJson(Gson.java:803)
    at com.google.gson.Gson.fromJson(Gson.java:768)
    at com.google.gson.Gson.fromJson(Gson.java:717)
    at com.google.gson.Gson.fromJson(Gson.java:689)
    at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:22)

不幸的是,EnumTypeAdapter 对它所调用的上下文一无所知,因此该解决方案不足以捕获字段名称。

编辑

因此,您还必须使用另一个TypeAdapter我调用CustomReflectiveTypeAdapterFactory的并且几乎是副本,CustomReflectiveTypeAdapterFactory并且我更改了一些异常,所以:

public final class CustomReflectiveTypeAdapterFactory implements TypeAdapterFactory {
  private final ConstructorConstructor constructorConstructor;
  private final FieldNamingStrategy fieldNamingPolicy;
  private final Excluder excluder;

  public CustomReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
      FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
    this.constructorConstructor = constructorConstructor;
    this.fieldNamingPolicy = fieldNamingPolicy;
    this.excluder = excluder;
  }

  public boolean excludeField(Field f, boolean serialize) {
    return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
  }

  private String getFieldName(Field f) {
    SerializedName serializedName = f.getAnnotation(SerializedName.class);
    return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
  }

  public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
    Class<? super T> raw = type.getRawType();

    if (!Object.class.isAssignableFrom(raw)) {
      return null; // it's a primitive!
    }

    ObjectConstructor<T> constructor = constructorConstructor.get(type);
    return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
  }

  private CustomReflectiveTypeAdapterFactory.BoundField createBoundField(
      final Gson context, final Field field, final String name,
      final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
    final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());

    // special casing primitives here saves ~5% on Android...
    return new CustomReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
      final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
      @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
      @Override void write(JsonWriter writer, Object value)
          throws IOException, IllegalAccessException {
        Object fieldValue = field.get(value);
        TypeAdapter t =
          new CustomTypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
        t.write(writer, fieldValue);
      }
      @Override void read(JsonReader reader, Object value)
          throws IOException, IllegalAccessException {
    Object fieldValue = null;
      try {
            fieldValue = typeAdapter.read(reader);
      } catch (GsonEnumParsinException e){
        e.setFieldName(field.getName());
        throw e;        
      }
        if (fieldValue != null || !isPrimitive) {
          field.set(value, fieldValue);
        }
      }
    };
  }
  // more copy&paste code follows

最重要的部分是read我捕获异常并添加字段名称并再次抛出异常的方法。请注意,类CustomTypeAdapterRuntimeTypeWrapper只是TypeAdapterRuntimeTypeWrapper库内部的重命名副本,因为类是私有的。

因此,主要方法更改如下:

 Map<Type, InstanceCreator<?>> instanceCreators
      = new HashMap<Type, InstanceCreator<?>>();

 Excluder excluder = Excluder.DEFAULT;
 FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;

GsonBuilder gb = new GsonBuilder(); 
gb.registerTypeAdapterFactory(new CustomReflectiveTypeAdapterFactory(new ConstructorConstructor(instanceCreators), fieldNamingPolicy, excluder));
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Gson g = gb.create();

现在你有了这个堆栈跟踪(对异常的更改非常简单,我省略了它们):

Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour, fieldName=colour]
    at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:90)
    at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
    at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$1.read(CustomReflectiveTypeAdapterFactory.java:79)
    at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$Adapter.read(CustomReflectiveTypeAdapterFactory.java:162)
    at com.google.gson.Gson.fromJson(Gson.java:803)
    at com.google.gson.Gson.fromJson(Gson.java:768)
    at com.google.gson.Gson.fromJson(Gson.java:717)
    at com.google.gson.Gson.fromJson(Gson.java:689)
    at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:35)

当然,这个解决方案需要付出一些代价。

  • 首先,您必须复制一些私有/最终类并进行更改。如果库得到更新,您必须再次检查您的代码(源代码的一个分支是相同的,但至少您不必复制所有代码)。
  • 如果您自定义字段排除策略、构造函数或字段命名策略,您必须将它们复制到 中,CustomReflectiveTypeAdapterFactory因为我找不到从构建器传递它们的任何可能性。
于 2013-09-08T10:43:16.687 回答