2017-04-12 119 views
1

如何让Gson反序列化计算值?Gson:将计算值反序列化

这是一个简化的示例:

@SerializedName("members") 
@Expose 
private final List<String> members; 

@SerializedName("size") 
@Expose(serialize = true, deserialize = false) 
private final int size; 

public Club(List<String> members) { 
    this.members = members; 
    this.size = members.size(); 
} 

我得到sizedeserializefalse = 0; 我得到size = old_sizedeserializetrue

有没有办法“强制”重新计算?

我知道我可以在吸气

回答

1

这是因为GSON不反序列化过程中调用构造函数做到这一点。 但是,可以拦截对象实例化后阶段,并对最近反序列化的对象进行一些额外的操作。

下面是一个例子@PostConstruct知晓型适配器厂:

final class PostConstructTypeAdapterFactory 
     implements TypeAdapterFactory { 

    // No intermediate state, can be a singleton 
    private static final TypeAdapterFactory postConstructTypeAdapterFactory = new PostConstructTypeAdapterFactory(); 

    private PostConstructTypeAdapterFactory() { 
    } 

    // However, making the constructor private encapsulates the way it's instantiated 
    static TypeAdapterFactory getPostConstructTypeAdapterFactory() { 
     return postConstructTypeAdapterFactory; 
    } 

    @Override 
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) { 
     final List<Method> postConstructMethods = getPostConstructMethods(typeToken.getRawType()); 
     if (postConstructMethods.isEmpty()) { 
      // If no post-construct methods found, just let Gson to pick up the next best-match type adapter itself 
      return null; 
     } 
     // Obtain the "original" type adapter 
     final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken); 
     return new PostConstructTypeAdapter<>(delegateTypeAdapter, postConstructMethods); 
    } 

    private static List<Method> getPostConstructMethods(final Class<?> clazz) { 
     if (clazz.isPrimitive()) { 
      // Nothing to do with primitives 
      return emptyList(); 
     } 
     // The following stream operation collects all `@PostConstruct` methods 
     // from java.lang.Object to the concrete class 
     // where all @PostConstruct methods must satisfy the following conditions (differ from the original `@PostConstruct` contract): 
     // * have no paramaters 
     // * return nothing (but it looks like a too strict limitation, though) 
     // * be annotated with `@PostConstruct` 
     return superToSub(clazz) 
       .stream() 
       .flatMap(c -> Stream.of(c.getDeclaredMethods())) 
       .filter(m -> { 
        final int parameterCount = m.getParameterCount(); 
        if (parameterCount != 0) { 
         return false; 
        } 
        final Class<?> returnType = m.getReturnType(); 
        if (returnType != void.class) { 
         return false; 
        } 
        return m.isAnnotationPresent(PostConstruct.class); 
       }) 
       .peek(m -> m.setAccessible(true)) 
       .collect(toList()); 
    } 

    private static List<Class<?>> superToSub(final Class<?> clazz) { 
     final List<Class<?>> hierarchy = subToSuper(clazz); 
     Collections.reverse(hierarchy); 
     return hierarchy; 
    } 

    private static List<Class<?>> subToSuper(final Class<?> clazz) { 
     final List<Class<?>> hierarchy = new ArrayList<>(); 
     for (Class<?> c = clazz; c != null; c = c.getSuperclass()) { 
      hierarchy.add(c); 
     } 
     return hierarchy; 
    } 

    private static final class PostConstructTypeAdapter<T> 
      extends TypeAdapter<T> { 

     private final TypeAdapter<T> delegateTypeAdapter; 
     private final Iterable<Method> postConstructMethods; 

     private PostConstructTypeAdapter(final TypeAdapter<T> delegateTypeAdapter, final Iterable<Method> postConstructMethods) { 
      this.delegateTypeAdapter = delegateTypeAdapter; 
      this.postConstructMethods = postConstructMethods; 
     } 

     @Override 
     public void write(final JsonWriter out, final T value) 
       throws IOException { 
      // Nothing special to do on write, so just delegate the job 
      delegateTypeAdapter.write(out, value); 
     } 

     @Override 
     public T read(final JsonReader in) 
       throws IOException { 
      try { 
       // Whilst on read there's a need to apply all post-construction methods 
       final T read = delegateTypeAdapter.read(in); 
       for (final Method method : postConstructMethods) { 
        method.invoke(read); 
       } 
       return read; 
      } catch (IllegalAccessException | InvocationTargetException ex) { 
       throw new IOException(ex); 
      } 
     } 

    } 

} 

然后,你Club可以包含后期构造:

@PostConstruct 
private void postConstruct() 
     throws NoSuchFieldException, IllegalAccessException { 
    final Field sizeField = Club.class.getDeclaredField("size"); 
    sizeField.setAccessible(true); 
    sizeField.set(this, members.size()); 
} 

例与即席测试使用:

private static final Gson gson = new GsonBuilder() 
     .excludeFieldsWithoutExposeAnnotation() 
     .registerTypeAdapterFactory(getPostConstructTypeAdapterFactory()) 
     .create(); 

public static void main(final String... args) { 
    final List<String> members = ImmutableList.of("Foo", "Bar", "Baz"); 
    final Club beforeClub = new Club(members); 
    final List<String> beforeMembers = beforeClub.members; 
    final int beforeSize = beforeClub.size; 
    final String clubJson = gson.toJson(beforeClub); 
    System.out.println(clubJson); 
    final Club afterClub = gson.fromJson(clubJson, Club.class); 
    final List<String> afterMembers = afterClub.members; 
    final int afterSize = afterClub.size; 
    if (!beforeMembers.equals(afterMembers)) { 
     throw new AssertionError("`members` values do not match"); 
    } 
    if (beforeSize != afterSize) { 
     throw new AssertionError("`size` values do not match"); 
    } 
    System.out.println("SUCCESS"); 
} 

输出:

{ “成员”:[ “富”, “酒吧”, “巴兹”], “大小”:3}
SUCCESS

详见:

+0

感谢您的详细回复。这感觉有点复杂,我不明白一些代码,但我会试一试。在getPostConstructMethods()的return语句中,有一个toList()方法。这是未定义的。 .collect()期望收集器参数? – sikidhart

+0

@sikidhart是的,这是一个收集器,这只是一个静态导入的'Collectors.toList'方法。 –