【问题标题】:Gson: can we get the serialized field name in a type adapter?Gson:我们可以在类型适配器中获取序列化的字段名称吗?
【发布时间】:2013-05-23 13:23:44
【问题描述】:

我发现 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 可以吗?

【问题讨论】:

    标签: java json parsing gson


    【解决方案1】:

    是的

    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 的重命名副本,因为类是私有的。

    所以,main方法变化如下:

     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 中,因为我找不到从构建器传递它们的任何可能性。

    【讨论】:

    • 这工作正常,但不完全是我想要的(但这是我最终所做的......)。正如我所说,我希望能够知道属性名称,而不是错误的枚举值,不是枚举名称,而是类字段名称
    • 好的,编辑也给出字段名称。将添加一些结论。
    • 谢谢。你和我几个月前的结论一样,我最终决定不复制这门课
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-29
    • 2020-10-12
    相关资源
    最近更新 更多