Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

adding an object using a custom typeadapter, jsonwriter in gson

Tags:

java

json

gson

gson is a great library - it works well. Sometimes I have custom requirements, and can make and register TypeAdapters and TypeAdaptorFactories - and that works well too.

What confuses me however, is how to delegate back into json serialization... Most of the time I need this for collections, but to illustrate the point - suppose I had a pair class, which gson would obviously serialize happily, but for some reason I needed my own custom serializer. Well... if my pair is

public class Pair
{
    public final Object First;
    public final Object Second; 
    public Pair( Object first, Object second) {this.first = first; this.second = second};
}

If I wrote a type adapter for this - you would want the write function to look like:

public void write( JsonWriter out, Pair pair )
{
    out.beginObject();
    out.name( "first");
    out.value( pair.first );         // can't do this
    out.name( "second");
    out.value( pair.second);         // or this
    out.endObject();
}

So you can see the problem - I have no idea the type of first and second, nor how they are serialized. I can use gson.toJson to serialize first and second - but if I add them as a string to the writer, they will be escaped. There is a gson.tojson function that takes a value and a writer - but it also takes a typetoken - which I don't have. I sort of get the impression I'm meant to have another type adapter from somewhere - but when I just have a list of objects... where do I get that? do I just get the adapter for object?

I'm a little confused? Surely this is the most common use case? Most custom serializers will be for a strange list of T or tree of T or something, and you really don't know what is in the list, beyond that it inherits from T... so you need to be able to delegate back the serialization in some way?

Anyway - if someone can tell me how to write the above function, I'd really appreciate it!

like image 885
Darren Oakey Avatar asked Jun 09 '13 01:06

Darren Oakey


2 Answers

In this case its better to use a JsonSerializer as opposed to a TypeAdapter, for the simple reason that serializers have access to their serialization context:

public class PairSerializer implements JsonSerializer<Pair> {

    public PairSerializer() {
        super();
    }

    @Override
    public JsonElement serialize(final Pair value, final Type type,
            final JsonSerializationContext context) {
        final JsonObject jsonObj = new JsonObject();
        jsonObj.add("first", context.serialize(value.getFirst()));
        jsonObj.add("second", context.serialize(value.getSecond()));

        return jsonObj;
    }
}

The above sample code illustrates how to delegate serialization of target objects back to the main marshaller. The main advantage of this (apart from avoiding complicated workarounds) is that you can still take advantage of other type adaptors and custom serializers that might have been registered in the main context. Note that registration of serializers and adapters use the exact same code:

// With adapter
final Gson gson = new GsonBuilder().registerTypeAdapter(Pair.class,
        new PairAdapter()).create();

// With serializer
final Gson gson = new GsonBuilder().registerTypeAdapter(Pair.class,
        new PairSerializer()).create();

If you find that you need to stick with an adapter, then you can use an embedded Gson proxy to serialize the Pair properties for you, with the disadvantage that you lose access to custom registrations that you made on the parent Gson proxy:

public class PairAdapter extends TypeAdapter<Pair> {
    final Gson embedded = new Gson();

    public PairAdapter() {
        super();
    }

    @Override
    public void write(final JsonWriter out, final Pair value)
            throws IOException {
        out.beginObject();

        out.name("first");
        embedded.toJson(embedded.toJsonTree(value.getFirst()), out);

        out.name("second");
        embedded.toJson(embedded.toJsonTree(value.getSecond()), out);

        out.endObject();
    }

    @Override
    public Pair read(JsonReader in) throws IOException {
        throw new UnsupportedOperationException();
    }
}
like image 79
Perception Avatar answered Nov 08 '22 06:11

Perception


actually that seemed to work - getting the adapter for object - using the typeadaptorfactory to discover a "json" method on an object, and having that store away the adaptor object. Seems astonishingly complex for what is such an obvious use case?

eg:

public static @Nullable
TypeAdapter<ImmutableStack<?>> json(final Gson gson)
{
    final TypeAdapter<Object> adaptor = gson.getAdapter(Object.class);
    return new TypeAdapter<ImmutableStack<?>>()
    {
        @Override
        public ImmutableStack<?> read(@Nullable final JsonReader in) throws IOException
        {
            throw ProgramError.notImplementedYet();
        }
        @Override
        public void write(final JsonWriter out, final ImmutableStack<?> value) throws IOException
        {
            out.beginArray();
            for (final Object inner : value)
                adaptor.write(out, inner);
            out.endArray();
        }
    };
}

does anyone know a better way to do it?

like image 4
Darren Oakey Avatar answered Nov 08 '22 07:11

Darren Oakey