javax.json serializer/deserializer 对于 Gson

javax.json serializer/deserializer for Gson

我正在尝试为 java.javax.JsonObjects 写一个通用的 Gson serializer/deserializer:

public static class JavaxJsonObjConverter implements JsonSerializer<JsonObject>, JsonDeserializer<JsonObject> {

  @Override
  public JsonObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    return JsonUtils.getJsonObjectFromString(json.toString());
  }

  @Override
  public JsonElement serialize(JsonObject src, Type typeOfSrc, JsonSerializationContext context) {
    return new JsonParser().parse(src.toString());
  }

}

当我尝试序列化 java.json.JsonObject 时,出现此错误:

Exception in thread "main" java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to javax.json.JsonObject
    at om.headlandstech.utils.gson_utils.GsonUtils$JavaxJsonValueConverter.serialize(>GsonUtils.java:1)
    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.MapTypeAdapterFactory$Adapter.write(MapTypeAdapte>rFactory.java:208)
    at ....

如果您也 post javax.json.JsonObject 实例(它们的构建方式),那就更好了。因为:最接近我可以重现它的是以下内容:

final Gson gson = new GsonBuilder()
        .registerTypeAdapter(JsonObject.class, new JavaxJsonObjConverter())
        .create();
final JsonObject src = Json.createObjectBuilder()
        .add("foo", "bar")
        .build();
System.out.println(gson.toJson(src.get("foo"), JsonObject.class));

异常:

Exception in thread "main" java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to javax.json.JsonObject
    at q43376802.Q43376802$JavaxJsonObjConverter.serialize(Q43376802.java:29)
    at com.google.gson.internal.bind.TreeTypeAdapter.write(TreeTypeAdapter.java:81)
    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 q43376802.Q43376802.main(Q43376802.java:26)

接下来。您的 JavaxJsonObjConverter 实现了 javax.json.JavaObject(反)序列化器,但 javax.json.JavaObject 不是 javax.json 中 JSON 对象的根。层次结构根是 JsonValue。所以你的(反)序列化器必须处理 JsonValue 而不是 JsonObject

public static class JavaxJsonValConverter
        implements JsonSerializer<JsonValue> {

    @Override
    public JsonElement serialize(final JsonValue jsonValue, final Type type, final JsonSerializationContext context) {
        return new JsonParser().parse(jsonValue.toString());
    }

}

并通过删除和完全删除 JavaxJsonObjConverter 来注册它:

.registerTypeAdapter(JsonValue.class, new JavaxJsonValConverter())

然而,上面的序列化器是天真的,需要更多的资源,但是,给你一些灵活性(当 reading/writing 直接 from/to JSON 流可能太不合理(比较 DOM 和 XML 中的 SAX -- 这是同一个故事)):

  • JsonSerializerJsonDeserializer 依赖 JSON 用 JsonElement 实现的树模型表示。这意味着必须将 entire JSON 加载到内存中,并且必须先构建其树模型,然后才能使用它。如果您要处理的 JSON 个对象很大,这将消耗更多内存。
  • toString()也是一个糟糕的选择:它需要先生成内部字符串,从而再次消耗内存。

所以,上面的项目可能会非常大 内存打印。为了节省内存资源,您可以创建一个可以与 JSON 流一起工作的 Gson TypeAdapter(这是 JSON 中每个(反)序列化器的基础)。

final class JsonValueTypeAdapter
        extends TypeAdapter<JsonValue> {

    private static final TypeAdapter<JsonValue> jsonValueTypeAdapter = new JsonValueTypeAdapter();

    private JsonValueTypeAdapter() {
    }

    static TypeAdapter<JsonValue> getJsonValueTypeAdapter() {
        return jsonValueTypeAdapter;
    }

    @Override
    public void write(final JsonWriter out, final JsonValue jsonValue)
            throws IOException {
        final ValueType valueType = jsonValue.getValueType();
        switch ( valueType ) {
        case ARRAY:
            JsonArrayTypeAdapter.instance.write(out, (JsonArray) jsonValue);
            break;
        case OBJECT:
            JsonObjectTypeAdapter.instance.write(out, (JsonObject) jsonValue);
            break;
        case STRING:
            JsonStringTypeAdapter.instance.write(out, (JsonString) jsonValue);
            break;
        case NUMBER:
            JsonNumberTypeAdapter.instance.write(out, (JsonNumber) jsonValue);
            break;
        case TRUE:
            JsonBooleanTypeAdapter.instance.write(out, jsonValue);
            break;
        case FALSE:
            JsonBooleanTypeAdapter.instance.write(out, jsonValue);
            break;
        case NULL:
            JsonNullTypeAdapter.instance.write(out, jsonValue);
            break;
        default:
            throw new AssertionError(valueType);
        }
    }

    @Override
    public JsonValue read(final JsonReader in)
            throws IOException {
        final JsonToken jsonToken = in.peek();
        switch ( jsonToken ) {
        case BEGIN_ARRAY:
            return JsonArrayTypeAdapter.instance.read(in);
        case END_ARRAY:
            throw new AssertionError("Must never happen due to delegation to the array type adapter");
        case BEGIN_OBJECT:
            return JsonObjectTypeAdapter.instance.read(in);
        case END_OBJECT:
            throw new AssertionError("Must never happen due to delegation to the object type adapter");
        case NAME:
            throw new AssertionError("Must never happen");
        case STRING:
            return JsonStringTypeAdapter.instance.read(in);
        case NUMBER:
            return JsonNumberTypeAdapter.instance.read(in);
        case BOOLEAN:
            return JsonBooleanTypeAdapter.instance.read(in);
        case NULL:
            return JsonNullTypeAdapter.instance.read(in);
        case END_DOCUMENT:
            throw new AssertionError("Must never happen");
        default:
            throw new AssertionError(jsonToken);
        }
    }

    private static final class JsonNullTypeAdapter
            extends TypeAdapter<JsonValue> {

        private static final TypeAdapter<JsonValue> instance = new JsonNullTypeAdapter().nullSafe();

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final JsonValue jsonNull)
                throws IOException {
            out.nullValue();
        }

        @Override
        public JsonValue read(final JsonReader in)
                throws IOException {
            in.nextNull();
            return JsonValue.NULL;
        }

    }

    private static final class JsonBooleanTypeAdapter
            extends TypeAdapter<JsonValue> {

        private static final TypeAdapter<JsonValue> instance = new JsonBooleanTypeAdapter().nullSafe();

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final JsonValue jsonBoolean)
                throws IllegalArgumentException, IOException {
            final ValueType valueType = jsonBoolean.getValueType();
            switch ( valueType ) {
            case TRUE:
                out.value(true);
                break;
            case FALSE:
                out.value(false);
                break;
            case ARRAY:
            case OBJECT:
            case STRING:
            case NUMBER:
            case NULL:
                throw new IllegalArgumentException("Not a boolean: " + valueType);
            default:
                throw new AssertionError(jsonBoolean.getValueType());
            }
        }

        @Override
        public JsonValue read(final JsonReader in)
                throws IOException {
            return in.nextBoolean() ? JsonValue.TRUE : JsonValue.FALSE;
        }

    }

    private static final class JsonNumberTypeAdapter
            extends TypeAdapter<JsonNumber> {

        private static final TypeAdapter<JsonNumber> instance = new JsonNumberTypeAdapter().nullSafe();

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final JsonNumber jsonNumber)
                throws IOException {
            if ( jsonNumber.isIntegral() ) {
                out.value(jsonNumber.longValue());
            } else {
                out.value(jsonNumber.doubleValue());
            }
        }

        @Override
        public JsonNumber read(final JsonReader in)
                throws IOException {
            // TODO is there a good way to instantiate a JsonNumber instance?
            return (JsonNumber) Json.createArrayBuilder()
                    .add(in.nextDouble())
                    .build()
                    .get(0);
        }

    }

    private static final class JsonStringTypeAdapter
            extends TypeAdapter<JsonString> {

        private static final TypeAdapter<JsonString> instance = new JsonStringTypeAdapter().nullSafe();

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final JsonString jsonString)
                throws IOException {
            out.value(jsonString.getString());
        }

        @Override
        public JsonString read(final JsonReader in)
                throws IOException {
            // TODO is there a good way to instantiate a JsonString instance?
            return (JsonString) Json.createArrayBuilder()
                    .add(in.nextString())
                    .build()
                    .get(0);
        }

    }

    private static final class JsonObjectTypeAdapter
            extends TypeAdapter<JsonObject> {

        private static final TypeAdapter<JsonObject> instance = new JsonObjectTypeAdapter().nullSafe();

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final JsonObject jsonObject)
                throws IOException {
            out.beginObject();
            for ( final Entry<String, JsonValue> e : jsonObject.entrySet() ) {
                out.name(e.getKey());
                jsonValueTypeAdapter.write(out, e.getValue());
            }
            out.endObject();
        }

        @Override
        public JsonObject read(final JsonReader in)
                throws IOException {
            final JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
            in.beginObject();
            while ( in.hasNext() ) {
                final String key = in.nextName();
                final JsonToken token = in.peek();
                switch ( token ) {
                case BEGIN_ARRAY:
                    jsonObjectBuilder.add(key, jsonValueTypeAdapter.read(in));
                    break;
                case END_ARRAY:
                    throw new AssertionError("Must never happen due to delegation to the array type adapter");
                case BEGIN_OBJECT:
                    jsonObjectBuilder.add(key, jsonValueTypeAdapter.read(in));
                    break;
                case END_OBJECT:
                    throw new AssertionError("Must never happen due to delegation to the object type adapter");
                case NAME:
                    throw new AssertionError("Must never happen");
                case STRING:
                    jsonObjectBuilder.add(key, in.nextString());
                    break;
                case NUMBER:
                    jsonObjectBuilder.add(key, in.nextDouble());
                    break;
                case BOOLEAN:
                    jsonObjectBuilder.add(key, in.nextBoolean());
                    break;
                case NULL:
                    in.nextNull();
                    jsonObjectBuilder.addNull(key);
                    break;
                case END_DOCUMENT:
                    // do nothing
                    break;
                default:
                    throw new AssertionError(token);
                }
            }
            in.endObject();
            return jsonObjectBuilder.build();
        }

    }

    private static final class JsonArrayTypeAdapter
            extends TypeAdapter<JsonArray> {

        private static final TypeAdapter<JsonArray> instance = new JsonArrayTypeAdapter().nullSafe();

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final JsonArray jsonArray)
                throws IOException {
            out.beginArray();
            for ( final JsonValue jsonValue : jsonArray ) {
                jsonValueTypeAdapter.write(out, jsonValue);
            }
            out.endArray();
        }

        @Override
        public JsonArray read(final JsonReader in)
                throws IOException {
            final JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder();
            in.beginArray();
            while ( in.hasNext() ) {
                final JsonToken token = in.peek();
                switch ( token ) {
                case BEGIN_ARRAY:
                    jsonArrayBuilder.add(jsonValueTypeAdapter.read(in));
                    break;
                case END_ARRAY:
                    throw new AssertionError("Must never happen due to delegation to the array type adapter");
                case BEGIN_OBJECT:
                    jsonArrayBuilder.add(jsonValueTypeAdapter.read(in));
                    break;
                case END_OBJECT:
                    throw new AssertionError("Must never happen due to delegation to the object type adapter");
                case NAME:
                    throw new AssertionError("Must never happen");
                case STRING:
                    jsonArrayBuilder.add(in.nextString());
                    break;
                case NUMBER:
                    jsonArrayBuilder.add(in.nextDouble());
                    break;
                case BOOLEAN:
                    jsonArrayBuilder.add(in.nextBoolean());
                    break;
                case NULL:
                    in.nextNull();
                    jsonArrayBuilder.addNull();
                    break;
                case END_DOCUMENT:
                    // do nothing
                    break;
                default:
                    throw new AssertionError(token);
                }
            }
            in.endArray();
            return jsonArrayBuilder.build();
        }

    }

}

上面的代码本身就是文档,尽管它相对较大。使用示例:

private static final Gson gson = new GsonBuilder()
        .serializeNulls()
        .registerTypeHierarchyAdapter(JsonValue.class, getJsonValueTypeAdapter())
        .create();

public static void main(final String... args) {
    final JsonValue before = createObjectBuilder()
            .add("boolean", true)
            .add("integer", 3)
            .add("string", "foo")
            .addNull("null")
            .add("array", createArrayBuilder()
                    .add(false)
                    .add(2)
                    .add("bar")
                    .addNull()
                    .build())
            .build();
    System.out.println("before.toString()   = " + before);
    final String json = gson.toJson(before);
    System.out.println("type adapter result = " + json);
    final JsonValue after = gson.fromJson(json, JsonValue.class);
    System.out.println("after.toString()    = " + after);
}

输出:

before.toString()   = {"boolean":true,"integer":3,"string":"foo","null":null,"array":[false,2,"bar",null]}
type adapter result = {"boolean":true,"integer":3,"string":"foo","null":null,"array":[false,2,"bar",null]}
after.toString()    = {"boolean":true,"integer":3.0,"string":"foo","null":null,"array":[false,2.0,"bar",null]}

请注意,integer 属性 值已更改:3 现在是 3.0。发生这种情况是因为 JSON 不区分整数、长整数、浮点数、双精度数等:它只能处理一个数字。您无法真正恢复原始数字:例如,3 可能既是 long 又是 double。您在这里最多可以做的就是不使用 .nextDouble() 而使用 .nextString() 并尝试检测它最适合哪种数字类型并分别构造一个 JsonNumber 实例(我想知道如何它可以在 javax.json 中完成——请参阅类型适配器中的 TODO 注释。