2017-04-12 120 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'方法。 –