javagsonunsupportedoperation

Gson throws UnsupportedOperationException on try to stringify object, which contains java.lang.Class as a field


I wrote ClassTypeAdapter, which is correct for classes, but fails when I'm trying to work with objects, which contains classes as fields.

The ClassTypeAdapter with executable main method to reproduce the problem attached.

Any ideas?

    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.TypeAdapter;
    import com.google.gson.stream.JsonReader;
    import com.google.gson.stream.JsonWriter;

    import java.io.IOException;

    public class ClassTypeAdapter extends TypeAdapter<Class<?>> {
        private static final String PARAM_NAME = "className";

        @Override
        public void write(JsonWriter out, Class<?> value) throws IOException {
            out.beginObject();

            out.name(PARAM_NAME).value(value.getName());

            out.endObject();
        }

        @Override
        public Class<?> read(JsonReader in) throws IOException {
            Class<?> readClass = null;

            in.beginObject();

            while (in.hasNext()) {
                if (PARAM_NAME.equals(in.nextName())) {
                    try {
                        readClass = Class.forName(in.nextString());
                    } catch (ClassNotFoundException e) {
                        throw new IOException("Class not found", e);
                    }
                }
            }

            in.endObject();

            return readClass;
        }

        public static class TestClass<T> {
            private Class<T> aClass;
        }

        public static void main(String[] args) {
            final Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassTypeAdapter()).create();

            final TestClass testClass = new TestClass<>();

            System.out.println(gson.toJson(testClass));    // {}
            System.out.println(gson.toJson(Object.class)); // {"className":"java.lang.Object"}

            testClass.aClass = Object.class;

            System.out.println(gson.toJson(testClass));    // UnsupportedOperationException
        }
    }

Stack trace of error:

Exception in thread "main" java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: java.lang.Object. Forgot to register a type adapter?
    at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:76)
    at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:69)
    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 ClassTypeAdapter.main(ClassTypeAdapter.java:56)

Solution

  • I've found a solution here.

    public class ClassTypeAdapter implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
        @Override
        public JsonElement serialize(Class<?> src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src.getName());
        }
    
        @Override
        public Class<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                return Class.forName(json.getAsString());
            } catch (ClassNotFoundException e) {
                throw new JsonParseException(e);
            }
        }
    }
    

    JUnit tests:

    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import org.junit.Test;
    
    import static org.junit.Assert.assertEquals;
    
    public class ClassTypeAdapterTest {
        @Test
        public void testReadWrite() {
            final Class<?> classToWrite = ClassTypeAdapter.class;
    
            final Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassTypeAdapter()).create();
    
            final String writtenClass = gson.toJson(classToWrite);
            final Class readClass = gson.fromJson(writtenClass, Class.class);
    
            assertEquals(classToWrite, readClass);
        }
    
        @Test
        public void testInnerClassProblem() {
            final Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassTypeAdapter()).create();
    
            final TestClass testClass = new TestClass<>();
            testClass.innerClass = Object.class;
    
            final String writtenClass = gson.toJson(testClass);
            final TestClass readClass = gson.fromJson(writtenClass, TestClass.class);
    
            assertEquals(testClass.innerClass, readClass.innerClass);
        }
    
        private static class TestClass<T> {
            private Class<T> innerClass;
        }
    }