2014-04-20 75 views
1

我想使用Gson將Guava Range對象序列化爲JSON,但默認序列化失敗,並且我不確定如何正確實現此泛型類型的TypeAdapter爲Guava範圍創建一個Gson TypeAdapter

Gson gson = new Gson(); 
Range<Integer> range = Range.closed(10, 20); 
String json = gson.toJson(range); 
System.out.println(json); 
Range<Integer> range2 = gson.fromJson(json, 
          new TypeToken<Range<Integer>>(){}.getType()); 
System.out.println(range2); 
assertEquals(range2, range); 

這種失敗,像這樣:

{"lowerBound":{"endpoint":10},"upperBound":{"endpoint":20}} 
PASSED: typeTokenInterface 
FAILED: range 
java.lang.RuntimeException: Unable to invoke no-args constructor for 
     com.google.common.collect.Cut<java.lang.Integer>. Register an 
     InstanceCreator with Gson for this type may fix this problem. 
    at com.google.gson.internal.ConstructorConstructor$12.construct(
     ConstructorConstructor.java:210) 
    ... 

注意,缺省的序列實際上丟失信息 - 它沒有報告端點是否打開或關閉。我希望看到它的序列化類似於它的toString(),例如,然而,簡單地調用toString()將不會與通用Range實例一起使用,因爲範圍的元素可能不是原語(例如,Joda-Time LocalDate實例)。出於同樣的原因,實現自定義TypeAdapter似乎很難,因爲我們不知道如何反序列化端點。

我已經實施了大部分TypeAdaptorFactory的基礎上爲Multimap提供的模板應該工作,但現在我卡在泛型。這是我到目前爲止有:

public class RangeTypeAdapterFactory implements TypeAdapterFactory { 
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { 
    Type type = typeToken.getType(); 
    if (typeToken.getRawType() != Range.class 
     || !(type instanceof ParameterizedType)) { 
     return null; 
    } 

    Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; 
    TypeAdapter<?> elementAdapter = (TypeAdapter<?>)gson.getAdapter(TypeToken.get(elementType)); 
    // Bound mismatch: The generic method newRangeAdapter(TypeAdapter<E>) of type 
    // GsonUtils.RangeTypeAdapterFactory is not applicable for the arguments 
    // (TypeAdapter<capture#4-of ?>). The inferred type capture#4-of ? is not a valid 
    // substitute for the bounded parameter <E extends Comparable<?>> 
    return (TypeAdapter<T>) newRangeAdapter(elementAdapter); 
    } 

    private <E extends Comparable<?>> TypeAdapter<Range<E>> newRangeAdapter(final TypeAdapter<E> elementAdapter) { 
    return new TypeAdapter<Range<E>>() { 
     @Override 
     public void write(JsonWriter out, Range<E> value) throws IOException { 
     if (value == null) { 
      out.nullValue(); 
      return; 
     } 

     String repr = (value.lowerBoundType() == BoundType.CLOSED ? "[" : "(") + 
         (value.hasLowerBound() ? elementAdapter.toJson(value.lowerEndpoint()) : "-\u221e") + 
         '\u2025' + 
         (value.hasLowerBound() ? elementAdapter.toJson(value.upperEndpoint()) : "+\u221e") + 
         (value.upperBoundType() == BoundType.CLOSED ? "]" : ")"); 
     out.value(repr); 
     } 

     public Range<E> read(JsonReader in) throws IOException { 
     if (in.peek() == JsonToken.NULL) { 
      in.nextNull(); 
      return null; 
     } 

     String[] endpoints = in.nextString().split("\u2025"); 
     E lower = elementAdapter.fromJson(endpoints[0].substring(1)); 
     E upper = elementAdapter.fromJson(endpoints[1].substring(0,endpoints[1].length()-1)); 

     return Range.range(lower, endpoints[0].charAt(0) == '[' ? BoundType.CLOSED : BoundType.OPEN, 
          upper, endpoints[1].charAt(endpoints[1].length()-1) == '[' ? BoundType.CLOSED : BoundType.OPEN); 
     } 
    }; 
    } 
} 

然而return (TypeAdapter<T>) newRangeAdapter(elementAdapter);線具有編譯錯誤,我現在處於虧損狀態來的。

解決此錯誤的最佳方法是什麼?有沒有更好的方法來序列化我錯過的對象Range?如果我想序列化RangeSet s呢?

而不是沮喪的是,谷歌工具庫和谷歌序列化庫似乎需要這麼多膠共同努力:(

+0

我想這個問題是混淆了'T'和範圍''。其實,我在你的代碼中看不到後者,但是'TypeAdapter >'是恕我直言,工廠應該返回。 – maaartinus

+0

@maaartinus是的,複雜的泛型是這個問題的重要組成部分。 'T'是指Gson要處理的類型(在這種情況下,'RangeSet '),所以'TypeAdapter '應該是正確的,但爲了處理'RangeSet ',我需要能夠處理'C's,'TypeAdaptor'並不真正可以訪問。我試圖複製['TypeAdaptorFactory'](http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/TypeAdapterFactory.html)中的'Multiset'示例,但我不確定這是可能的。 – dimo414

+0

我看到我完全錯了。我想你的'elementAdapter'必須是'TypeAdapter >',但我不知道如果沒有骯髒的黑客攻擊是可能的。當首先投射到「對象」時,可以將任何東西投射到任何東西上。當然,它是不安全和骯髒的,但有時它是唯一的方法。在獲得黑客工作後,我經常找到更好的解決方案。 – maaartinus

回答

1

這種感覺有點像重新發明輪子,但它是快了很多放在一起和測試比所花的時間試圖讓GSON的行爲,所以至少目前我會使用以下Converter s到序列RangeRangeSet *,而不是GSON。

/** 
* Converter between Range instances and Strings, essentially a custom serializer. 
* Ideally we'd let Gson or Guava do this for us, but presently this is cleaner. 
*/ 
public static <T extends Comparable<? super T>> Converter<Range<T>, String> rangeConverter(final Converter<T, String> elementConverter) { 
    final String NEG_INFINITY = "-\u221e"; 
    final String POS_INFINITY = "+\u221e"; 
    final String DOTDOT = "\u2025"; 
    return new Converter<Range<T>, String>() { 
    @Override 
    protected String doForward(Range<T> range) { 
     return (range.hasLowerBound() && range.lowerBoundType() == BoundType.CLOSED ? "[" : "(") + 
      (range.hasLowerBound() ? elementConverter.convert(range.lowerEndpoint()) : NEG_INFINITY) + 
      DOTDOT + 
      (range.hasUpperBound() ? elementConverter.convert(range.upperEndpoint()) : POS_INFINITY) + 
      (range.hasUpperBound() && range.upperBoundType() == BoundType.CLOSED ? "]" : ")"); 
    } 

    @Override 
    protected Range<T> doBackward(String range) { 
     String[] endpoints = range.split(DOTDOT); 

     Range<T> ret = Range.all(); 
     if(!endpoints[0].substring(1).equals(NEG_INFINITY)) { 
     T lower = elementConverter.reverse().convert(endpoints[0].substring(1)); 
     ret = ret.intersection(Range.downTo(lower, endpoints[0].charAt(0) == '[' ? BoundType.CLOSED : BoundType.OPEN)); 
     } 
     if(!endpoints[1].substring(0,endpoints[1].length()-1).equals(POS_INFINITY)) { 
     T upper = elementConverter.reverse().convert(endpoints[1].substring(0,endpoints[1].length()-1)); 
     ret = ret.intersection(Range.upTo(upper, endpoints[1].charAt(endpoints[1].length()-1) == ']' ? BoundType.CLOSED : BoundType.OPEN)); 
     } 
     return ret; 
    } 
    }; 
} 

/** 
* Converter between RangeSet instances and Strings, essentially a custom serializer. 
* Ideally we'd let Gson or Guava do this for us, but presently this is cleaner. 
*/ 
public static <T extends Comparable<? super T>> Converter<RangeSet<T>, String> rangeSetConverter(final Converter<T, String> elementConverter) { 
    return new Converter<RangeSet<T>, String>() { 
    private final Converter<Range<T>, String> rangeConverter = rangeConverter(elementConverter); 
    @Override 
    protected String doForward(RangeSet<T> rs) { 
     ArrayList<String> ls = new ArrayList<>(); 
     for(Range<T> range : rs.asRanges()) { 
     ls.add(rangeConverter.convert(range)); 
     } 
     return Joiner.on(", ").join(ls); 
    } 

    @Override 
    protected RangeSet<T> doBackward(String rs) { 
     Iterable<String> parts = Splitter.on(",").trimResults().split(rs); 
     ImmutableRangeSet.Builder<T> build = ImmutableRangeSet.builder(); 
     for(String range : parts) { 
     build.add(rangeConverter.reverse().convert(range)); 
     } 
     return build.build(); 
    } 
    }; 
} 

*對於進程間通信,Java序列化可能會工作得很好,因爲兩者都可以類實現Serializable。不過,我正在序列化磁盤以獲得更多的永久性存儲空間,這意味着我需要一種我可以信賴的格式不會隨着時間而改變。番石榴的系列化doesn't provide that guarantee

+0

如果你想堅持序列化,你可以保存自己的類型。我想,一個包含邊界和它們的類型的簡單類都可以。就個人而言,我相信「實際上,序列化表格不會經常改變」的句子,添加相應的測試,並準備好進行轉換。 – maaartinus

+0

這很公平。我想補充一點,我也更喜歡我的解決方案來Java序列化,因爲它是人類可讀的,但承認這不會成爲這種用例的破壞者。 – dimo414

0

這裏是一個GSON JsonSerializer和JsonDeserializer是一般支持範圍:https://github.com/jamespedwards42/Fava/wiki/Range-Marshaller

@Override 
public JsonElement serialize(final Range src, final Type typeOfSrc, final JsonSerializationContext context) { 
    final JsonObject jsonObject = new JsonObject(); 
    if (src.hasLowerBound()) { 
     jsonObject.add("lowerBoundType", context.serialize(src.lowerBoundType())); 
     jsonObject.add("lowerBound", context.serialize(src.lowerEndpoint())); 
    } else 
     jsonObject.add("lowerBoundType", context.serialize(BoundType.OPEN)); 

    if (src.hasUpperBound()) { 
     jsonObject.add("upperBoundType", context.serialize(src.upperBoundType())); 
     jsonObject.add("upperBound", context.serialize(src.upperEndpoint())); 
    } else 
     jsonObject.add("upperBoundType", context.serialize(BoundType.OPEN)); 
    return jsonObject; 
} 

@Override 
public Range<? extends Comparable<?>> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { 
    if (!(typeOfT instanceof ParameterizedType)) 
     throw new IllegalStateException("typeOfT must be a parameterized Range."); 

    final JsonObject jsonObject = json.getAsJsonObject(); 
    final JsonElement lowerBoundTypeJsonElement = jsonObject.get("lowerBoundType"); 
    final JsonElement upperBoundTypeJsonElement = jsonObject.get("upperBoundType"); 

    if (lowerBoundTypeJsonElement == null || upperBoundTypeJsonElement == null) 
     throw new IllegalStateException("Range " + json 
       + "was not serialized with this serializer! The default serialization does not store the boundary types, therfore we can not deserialize."); 

    final Type type = ((ParameterizedType) typeOfT).getActualTypeArguments()[0]; 

    final BoundType lowerBoundType = context.deserialize(lowerBoundTypeJsonElement, BoundType.class); 
    final JsonElement lowerBoundJsonElement = jsonObject.get("lowerBound"); 
    final Comparable<?> lowerBound = lowerBoundJsonElement == null ? null : context.deserialize(lowerBoundJsonElement, type); 

    final BoundType upperBoundType = context.deserialize(upperBoundTypeJsonElement, BoundType.class); 
    final JsonElement upperBoundJsonElement = jsonObject.get("upperBound"); 
    final Comparable<?> upperBound = upperBoundJsonElement == null ? null : context.deserialize(upperBoundJsonElement, type); 

    if (lowerBound == null && upperBound != null) 
     return Range.upTo(upperBound, upperBoundType); 
    else if (lowerBound != null && upperBound == null) 
     return Range.downTo(lowerBound, lowerBoundType); 
    else if (lowerBound == null && upperBound == null) 
     return Range.all(); 

    return Range.range(lowerBound, lowerBoundType, upperBound, upperBoundType); 
} 
+0

非常好,謝謝分享!當我有機會時,我會試試這個。 – dimo414