简体   繁体   中英

javax.json serializer/deserializer for Gson

I'm trying to write a general Gson serializer/deserializer for java.javax.JsonObject s:

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());
  }

}

When I try to serialize a java.json.JsonObject , I get this error:

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

It would be much better if you'd post the javax.json.JsonObject instance as well (they way it's built). Because: the closest I can reproduce it with is the following:

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:

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)

Next thing. Your JavaxJsonObjConverter implements a javax.json.JavaObject (de)serializer, but javax.json.JavaObject is not the root of JSON objects in javax.json . The hierarchy root is JsonValue . So your (de)serializer must deal with JsonValue rather than 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());
    }

}

And register it with removing and deleting JavaxJsonObjConverter entirely:

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

However, the serializer above is naive and requires more resources however, giving you some flexibility (when reading/writing directly from/to JSON streams may be too unjustified (compare DOM and SAX in XML -- it's the same story)):

  • JsonSerializer and JsonDeserializer rely on JSON tree model representation that's implemented with JsonElement . This means that entire JSON has to be loaded into memory and its tree model has to be built before you can use it. This would consume much more memory if JSON objects you're going to deal with are large.
  • toString() is a bad choice either: it requires internal strings to be generated first, thus consuming memory again.

So, the items above may make a really large memory print. In order to save memory resources, you can create a Gson TypeAdapter that can work with JSON streams (and that is the base for every (de)serializer in 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();
        }

    }

}

The code above is itself-document I think, despite it's relatively large. Example use:

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);
}

Output:

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]}

Note that the integer property value has been changed: 3 is now 3.0 . This happens because JSON does not distinguish between integers, longs, floats, doubles, etc: all it can handle is just a number. You cannot really restore the original number: for example, 3 may be both long and double . The most you can do here is not using .nextDouble() in favor of .nextString() and trying to detect which numeric type it can fit the most and constuct a JsonNumber instance respectively (I'm wondering how it can be done in javax.json -- see the TODO comments in the type adapter).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM