分配空值'在Double字段的Gson自定义序列化中

时间:2017-02-16 15:31:07

标签: java serialization gson

我正在尝试使用Gson自定义序列化将以下类序列化为Json。

public class Product{
   public String prodId;
   public Double prodPrice;
}

我想检查Double属性(prodPrice)的值是否为" -1.0"并且如果它是" -1.0"则为该字段分配空值。我已经编写了以下Gson自定义序列化程序来执行此操作。

public class GsonUtil {

public static String getJSONString(Object input) throws Exception {
    if (input == null) {
        throw new Exception("JSON Conversion failed. Input string is null");
    }

    try {

        JsonSerializer<Double> dser = getDoubleSerializer();

        Gson gson = new GsonBuilder().serializeNulls()
                .registerTypeAdapter(Double.class, dser)
                .create();

        String jsonStr = gson.toJson(input);

        if (jsonStr == null) {
            throw new Exception("Json String is null");
        }
        return jsonStr;

    } catch (Exception e) {

    }
    return null;
}

private static JsonSerializer<Double> getDoubleSerializer() {
    JsonSerializer<Double> ser = new JsonSerializer<Double>() {


        public JsonElement serialize(Double dval, Type arg1,
                JsonSerializationContext arg2) {

            if(!(dval.equals(-1.0))){
                return new JsonPrimitive(dval);
            }
            else{
                Double retVal = null;
                return new JsonPrimitive(retVal);
            }

        }
    };
    return ser;
}

}

当我这样做时,自定义序列化程序不起作用,当我尝试分配除null之外的其他一些值时,相同的自定义序列化工作正常。

如果我做错了,请告诉我,如果可以通过其他方式实现,请告诉我。 谢谢。

1 个答案:

答案 0 :(得分:0)

每次可能需要这样的反序列化时,您可以让一个Gson实例不创建另一个实例。 Gson支持可以绑定到特定对象字段并使用@JsonAdapter注释的特殊类型适配器。请注意,它不会影响相同类型的其他对象,除非使用此批注进行批注,因此保留原始Gson配置。

因此Product.java可以拥有prodPrice字段的注释:

final class Product {

    @SerializedName("prodId")
    String id;

    @JsonAdapter(SpecialDoubleTypeAdapter.class)
    @SerializedName("prodPrice")
    Double price;

}

现在只需创建一个符合您要求的特殊类型适配器:

final class SpecialDoubleTypeAdapter
        extends TypeAdapter<Double> {

    private static final TypeAdapter<Double> specialDoubleTypeAdapter = new SpecialDoubleTypeAdapter();

    // Gson can instantiate it itself even with a private constructor
    private SpecialDoubleTypeAdapter() {
    }

    // In case if it's necessary to instantiate it manually
    static TypeAdapter<Double> getSpecialDoubleTypeAdapter() {
        return specialDoubleTypeAdapter;
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final Double value)
            throws IOException {
        // write the value as null if no value provided or it's considered "illegal" (however it's a good place to review and the right-hand expression)
        if ( value != null || value < 0 ) {
            out.value(value);
        } else {
            out.nullValue();
        }
    }

    @Override
    public Double read(final JsonReader in)
            throws IOException {
        final JsonToken token = in.peek();
        switch ( token ) {
        case NULL:
            // read the null token, this is mandatory
            in.nextNull();
            return null;
        case NUMBER:
            // read the number token as double
            final double d = in.nextDouble();
            // assuming the negative price should be null, not just -1.0
            return d >= 0 ? d : null;
        // an opinion-based case group: you can use default
        // but in my opinion explicit mapping is explicit
        case BEGIN_ARRAY:
        case END_ARRAY:
        case BEGIN_OBJECT:
        case END_OBJECT:
        case NAME:
        case STRING:
        case BOOLEAN:
        case END_DOCUMENT:
            throw new MalformedJsonException("Unexpected token: " + token);
        // so this would never happen unless Gson adds a new token some day...
        default:
            throw new AssertionError("must never happen");
        }
    }

}

然后做一点测试:

public static void main(final String... args) {
    final Gson gson = new Gson();
    test(gson, "{\"prodId\":\"good\",\"prodPrice\":100}");
    test(gson, "{\"prodId\":\"bad\",\"prodPrice\":-1}");
}

private static void test(final Gson gson, final String json) {
    final Product product = gson.fromJson(json, Product.class);
    out.print("Product ");
    out.print(product.id);
    out.print(", price ");
    out.println(product.price);
}

上面的演示将产生:

  

产品好,价格100.0
  产品不好,价格无效

编辑1

您的序列化程序存在一些设计问题。主要的一点是你的代码在发生时会吞下一个异常,而且很抱歉,当我看到Double retVal = null; return new JsonPrimitive(retVal);时,我已经错过了它。请不要那样做 - 或者你的代码会为严肃的问题保持沉默。接下来,您可以重新抛出异常,这就是它:

java.lang.NullPointerException
    at com.google.gson.JsonPrimitive.isPrimitiveOrString(JsonPrimitive.java:278)
    at com.google.gson.JsonPrimitive.setValue(JsonPrimitive.java:101)
    at com.google.gson.JsonPrimitive.<init>(JsonPrimitive.java:56)
    at q42278197.m2.Q42278197$1.serialize(Q42278197.java:77)
    at q42278197.m2.Q42278197$1.serialize(Q42278197.java:71)
    at com.google.gson.internal.bind.TreeTypeAdapter.write(TreeTypeAdapter.java:81)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:125)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:243)
    at com.google.gson.Gson.toJson(Gson.java:669)
    at com.google.gson.Gson.toJson(Gson.java:648)
    at com.google.gson.Gson.toJson(Gson.java:603)
    at com.google.gson.Gson.toJson(Gson.java:583)
    at q42278197.m2.Q42278197.getJSONString(Q42278197.java:58)
    ... 7 more

根本原因是:Gson中的Class<?> classOfPrimitive = target.getClass();。 Gson有一个特殊的JSON null元素:JsonNull单例,只有一个实例INSTANCE。因此,JsonSerializer可能会将其归还为现有值:return JsonNull.INSTANCE;

这是一个经过重新设计的实施:

private static String toJsonBySpecialDoubles(final Object input)
        throws NullPointerException {
    if ( input == null ) {
        throw new NullPointerException("input");
    }
    return specialDoubleGson.toJson(input);
}

private static final Gson specialDoubleGson = new GsonBuilder()
        .serializeNulls()
        .registerTypeAdapter(Double.class, getSpecialDoubleSerializer())
        .create();

private static JsonSerializer<Double> getSpecialDoubleSerializer() {
    return (value, type, context) -> !value.equals(-1.0) ? new JsonPrimitive(value) : JsonNull.INSTANCE;
}

请注意,使用了Java 8 lambda,但您可以轻松地将其转换为匿名类。另请注意,您可以拥有一个Gson实例。这可能就是你所需要的,因为Gson本身可以处理空值,等等。