Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson & Java - Attempted to serialize java.lang.Class: ..... Forgot to register a type adapter?

Tags:

java

gson

I'm trying to create an abstract class for defining configuration classes. I wish to export and import these classes from and to JSON whenever I want to. I'm trying to achieve this using Gson.

I'm getting an error when writing to JSON that states it:

can't serialize java.lang.Class - Forgot to register a type adapter?

My main class: https://hastebin.com/pogohodovi.scala
Abstract config class: https://hastebin.com/adeyawubuy.cs

An example of a child class:

public class DyescapeCOREConfiguration extends DyescapeConfiguration {

    private static transient DyescapeCOREConfiguration i = new DyescapeCOREConfiguration();
    public static DyescapeCOREConfiguration get() { return i; }

    @Expose public static String ServerID = UUID.randomUUID().toString();

}

Please note: I need to keep the variables in the child configuration classes static. I tried to create some adapters/serializers, but they don't seem to work.

like image 915
Dennis van der Veeke Avatar asked Jan 30 '17 13:01

Dennis van der Veeke


People also ask

What is Gson used for?

Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object.

What is Gson vs JSON?

GSON can use the Object definition to directly create an object of the desired type. While JSONObject needs to be parsed manually.

Is Gson an API?

Defines the Gson serialization/deserialization API.

Why Gson is used in Java?

Overview. Google's Gson library provides a powerful framework for converting between JSON strings and Java objects. This library helps to avoid needing to write boilerplate code to parse JSON responses yourself. It can be used with any networking library, including the Android Async HTTP Client and OkHttp.


1 Answers

You're probably doing:

gson.toJson(DyescapeCOREConfiguration.class)

In order to serialize this class, you still must create an instance of DyescapeCOREConfiguration. Since statics are not (de)serialized by default, you have to enable them (IMHO, enabling such modifier is really not a good idea):

    final Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .excludeFieldsWithModifiers(TRANSIENT) // STATIC|TRANSIENT in the default configuration
            .create();
    final String json = gson.toJson(new DyescapeCOREConfiguration());
    System.out.println(json);

The output:

{"ServerID":"37145480-64b9-4beb-b031-2d619f14a44b"}


Update

If obtaining an instance is not possible for whatever reason, write a custom Class<?> type adapter (I would never use it in practice):

StaticTypeAdapterFactory.java

final class StaticTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory staticTypeAdapterFactory = new StaticTypeAdapterFactory();

    private StaticTypeAdapterFactory() {
    }

    static TypeAdapterFactory getStaticTypeAdapterFactory() {
        return staticTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        final Type type = typeToken.getType();
        if ( type.equals(Class.class) ) {
            @SuppressWarnings("unchecked")
            final TypeAdapter<T> castStaticTypeAdapter = (TypeAdapter<T>) getStaticTypeAdapter(gson);
            return castStaticTypeAdapter;
        }
        return null;
    }

}

StaticTypeAdapter.java

final class StaticTypeAdapter<T>
        extends TypeAdapter<Class<T>> {

    private static final String TARGET_CLASS_PROPERTY = "___class";

    private final Gson gson;

    private StaticTypeAdapter(final Gson gson) {
        this.gson = gson;
    }

    static <T> TypeAdapter<Class<T>> getStaticTypeAdapter(final Gson gson) {
        return new StaticTypeAdapter<>(gson);
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final Class<T> value)
            throws IOException {
        try {
            final Iterator<Field> iterator = Stream.of(value.getFields())
                    .filter(f -> isStatic(f.getModifiers()))
                    .iterator();
            out.beginObject();
            while ( iterator.hasNext() ) {
                final Field field = iterator.next();
                out.name(field.getName());
                field.setAccessible(true);
                final Object fieldValue = field.get(null);
                @SuppressWarnings({ "unchecked", "rawtypes" })
                final TypeAdapter<Object> adapter = (TypeAdapter) gson.getAdapter(field.getType());
                adapter.write(out, fieldValue);
            }
            out.name(TARGET_CLASS_PROPERTY);
            out.value(value.getName());
            out.endObject();
        } catch ( final IllegalAccessException ex ) {
            throw new IOException(ex);
        }
    }

    @Override
    public Class<T> read(final JsonReader in)
            throws IOException {
        try {
            Class<?> type = null;
            in.beginObject();
            final Map<String, JsonElement> buffer = new HashMap<>();
            while ( in.peek() != END_OBJECT ) {
                final String property = in.nextName();
                switch ( property ) {
                case TARGET_CLASS_PROPERTY:
                    type = Class.forName(in.nextString());
                    break;
                default:
                    // buffer until the target class name is known
                    if ( type == null ) {
                        final TypeAdapter<JsonElement> adapter = gson.getAdapter(JsonElement.class);
                        final JsonElement jsonElement = adapter.read(in);
                        buffer.put(property, jsonElement);
                    } else {
                        // flush the buffer
                        if ( !buffer.isEmpty() ) {
                            for ( final Entry<String, JsonElement> e : buffer.entrySet() ) {
                                final Field field = type.getField(e.getKey());
                                final Object value = gson.getAdapter(field.getType()).read(in);
                                field.set(null, value);
                            }
                            buffer.clear();
                        }
                        final Field field = type.getField(property);
                        if ( isStatic(field.getModifiers()) ) {
                            final TypeAdapter<?> adapter = gson.getAdapter(field.getType());
                            final Object value = adapter.read(in);
                            field.set(null, value);
                        }
                    }
                    break;
                }
            }
            in.endObject();
            // flush the buffer
            if ( type != null && !buffer.isEmpty() ) {
                for ( final Entry<String, JsonElement> e : buffer.entrySet() ) {
                    final Field field = type.getField(e.getKey());
                    final Object value = gson.fromJson(e.getValue(), field.getType());
                    field.set(null, value);
                }
                buffer.clear();
            }
            @SuppressWarnings({ "unchecked", "rawtypes" })
            final Class<T> castType = (Class) type;
            return castType;
        } catch ( final ClassNotFoundException | NoSuchFieldException | IllegalAccessException ex ) {
            throw new IOException(ex);
        }
    }

}

Example use:

final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getStaticTypeAdapterFactory())
        .create();
final String json = gson.toJson(DyescapeCOREConfiguration.class);
out.println("DyescapeCOREConfiguration.ServerID=" + DyescapeCOREConfiguration.ServerID);
// ---
DyescapeCOREConfiguration.ServerID = "whatever";
out.println("DyescapeCOREConfiguration.ServerID=" + DyescapeCOREConfiguration.ServerID);
// ---
@SuppressWarnings("unchecked")
final Class<DyescapeCOREConfiguration> configurationClass = gson.fromJson(json, Class.class);
//    ^--- this is awful, omitting a useless assignment is even worse
out.println("DyescapeCOREConfiguration.ServerID=" + DyescapeCOREConfiguration.ServerID);

Output:

DyescapeCOREConfiguration.ServerID=012fa795-abd8-4b91-b6f5-bab67f73ae17
DyescapeCOREConfiguration.ServerID=whatever
DyescapeCOREConfiguration.ServerID=012fa795-abd8-4b91-b6f5-bab67f73ae17

However, I still recommend you to avoid the idea of static fields (de)serialization.

like image 120
Lyubomyr Shaydariv Avatar answered Sep 30 '22 00:09

Lyubomyr Shaydariv