为番石榴系列创建一个Gson TypeAdapter

时间:2014-04-20 06:02:21

标签: serialization range gson guava rangeset

我正在尝试使用Gson将Guava Range对象序列化为JSON,但默认序列化失败,我不确定如何为此泛型类型正确实现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(),例如[10‥20]但是简单地调用toString()将无法使用通用Range实例,因为范围的元素可能不是基元(例如,Joda-Time LocalDate实例) 。出于同样的原因,实现自定义TypeAdapter似乎很困难,因为我们不知道如何反序列化端点。

我已根据为Multimap提供的模板实现了TypeAdaptorFactory的大部分内容,这应该有效,但现在我仍然坚持使用泛型。这是我到目前为止所做的:

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怎么办?

相当令人沮丧的是,Google实用程序库和Google序列化库似乎需要这么多胶水才能协同工作:(

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

这是一个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 );
}

答案 2 :(得分:0)

这是一个简单的解决方案。效果很好

import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import com.google.gson.*;

import java.lang.reflect.Type;

public class GoogleRangeAdapter implements JsonSerializer, JsonDeserializer {
    public static String TK_hasLowerBound = "hasLowerBound";
    public static String TK_hasUpperBound = "hasUpperBound";

    public static String TK_lowerBoundType = "lowerBoundType";
    public static String TK_upperBoundType = "upperBoundType";

    public static String TK_lowerBound = "lowerBound";
    public static String TK_upperBound = "upperBound";

    @Override
    public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject = (JsonObject)json;
        boolean hasLowerBound = jsonObject.get(TK_hasLowerBound).getAsBoolean();
        boolean hasUpperBound = jsonObject.get(TK_hasUpperBound).getAsBoolean();

        if (!hasLowerBound && !hasUpperBound) {
            return Range.all();
        }
        else if (!hasLowerBound && hasUpperBound){
            double upperBound = jsonObject.get(TK_upperBound).getAsDouble();
            BoundType upperBoundType = BoundType.valueOf(jsonObject.get(TK_upperBoundType).getAsString());
            if (upperBoundType == BoundType.OPEN)
                return Range.lessThan(upperBound);
            else
                return Range.atMost(upperBound);
        }
        else if (hasLowerBound && !hasUpperBound){
            double lowerBound = jsonObject.get(TK_lowerBound).getAsDouble();
            BoundType lowerBoundType = BoundType.valueOf(jsonObject.get(TK_lowerBoundType).getAsString());
            if (lowerBoundType == BoundType.OPEN)
                return Range.greaterThan(lowerBound);
            else
                return Range.atLeast(lowerBound);
        }
        else {
            double lowerBound = jsonObject.get(TK_lowerBound).getAsDouble();
            double upperBound = jsonObject.get(TK_upperBound).getAsDouble();
            BoundType upperBoundType = BoundType.valueOf(jsonObject.get(TK_upperBoundType).getAsString());
            BoundType lowerBoundType = BoundType.valueOf(jsonObject.get(TK_lowerBoundType).getAsString());
            if (lowerBoundType == BoundType.OPEN && upperBoundType == BoundType.OPEN)
                return Range.open(lowerBound, upperBound);
            else if (lowerBoundType == BoundType.OPEN && upperBoundType == BoundType.CLOSED)
                return Range.openClosed(lowerBound, upperBound);
            else if (lowerBoundType == BoundType.CLOSED && upperBoundType == BoundType.OPEN)
                return Range.closedOpen(lowerBound, upperBound);
            else
                return Range.closed(lowerBound, upperBound);
        }
    }

    @Override
    public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject jsonObject = new JsonObject();

        Range<Double> range = (Range<Double>)src;
        boolean hasLowerBound = range.hasLowerBound();
        boolean hasUpperBound = range.hasUpperBound();

        jsonObject.addProperty(TK_hasLowerBound, hasLowerBound);
        jsonObject.addProperty(TK_hasUpperBound, hasUpperBound);

        if (hasLowerBound) {
            jsonObject.addProperty(TK_lowerBound, range.lowerEndpoint());
            jsonObject.addProperty(TK_lowerBoundType, range.lowerBoundType().name());
        }

        if (hasUpperBound) {
            jsonObject.addProperty(TK_upperBound, range.upperEndpoint());
            jsonObject.addProperty(TK_upperBoundType, range.upperBoundType().name());
        }

        return jsonObject;
    }
}