【问题标题】:javax.json serializer/deserializer for GsonGson 的 javax.json 序列化器/反序列化器
【发布时间】:2026-01-20 20:00:01
【问题描述】:

我正在尝试为java.javax.JsonObjects 编写一个通用的 Gson 序列化器/反序列化器:

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 ....

【问题讨论】:

    标签: java json serialization gson javax.json


    【解决方案1】:

    如果您也发布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 (de)serializer,但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())
    

    但是,上面的序列化程序很幼稚,需要更多资源,但给您一些灵活性(当直接从/向 JSON 流读取/写入时可能太不合理(比较 XML 中的 DOM 和 SAX - 这是同一个故事)) :

    • JsonSerializerJsonDeserializer 依赖于使用 JsonElement 实现的 JSON 树模型表示。这意味着必须将整个 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 实例(我想知道如何在@ 987654351@ -- 查看类型适配器中的 TODO cmets。

    【讨论】:

    • 非常感谢。这是一个巨大的帮助。我绝对了解这些东西现在如何更好地工作。但是有一个问题:为什么布尔值会被序列化为 null?有没有简单的方法来防止这种情况?他们甚至没有实现类型适配器功能。它们在 gson 代码中被序列化为 null。
    • @piyo 抱歉,我没注意到。请给我一些时间,以便我检查一下。
    • @piyo 完成:我忘记使用.registerTypeHierarchyAdapter 添加类型适配器,以便它可以管理整个层次结构。现在它应该可以工作了。
    • 太棒了!非常感谢。这没什么大不了的,因为我可以解决它,但我注意到即使使用类型层次结构适配器,如果对象的运行时类型是 org.glassfish.json.jsonObjectBuilderImpl.JsonObjectImpl,gson 也不会选择正确的适配器。这是为什么?我只是想更好地了解机器的工作原理。
    • @piyo 即使使用类型层次适配器,如果对象的运行时类型是 org.glassfish.json.jsonObjectBuilderImpl.JsonObjectImpl,gson 也不会选择正确的适配器 ——能详细点吗?对于哪些情况以及Gson 对象配置是什么?