Custom Serializers fail if the serializer context is used on the source #43

Closed
GoogleCodeExporter opened this Issue Mar 19, 2015 · 16 comments

Comments

Projects
None yet
1 participant
@GoogleCodeExporter
Gson will fail to serialize a class "Foo" if you create a custom serializer
as follows:

public static class FooTypeAdapter implements JsonSerializer<Foo> {
  public JsonElement serialize(Foo src, Type typeOfSrc,
JsonSerializationContext context) {
    return context.serialize(src, typeOfSrc);
  }
}

Basically, it detects this as a circular reference.  Instead, we should
allow this kind of custom serializer because a client may want to perform
the default serialization of an object and then add new fields into the
JsonElement tree.

For example:
public static class FooTypeAdapter implements JsonSerializer<Foo> {
  public JsonElement serialize(Foo src, Type typeOfSrc,
JsonSerializationContext context) {
    JsonElement element = context.serialize(src, typeOfSrc);
    JsonObject jsonObject = element.getAsJsonObject();
    jsonObject.add("someNewProperty", new JsonPrimitive(1L));
    return jsonObject;
  }
}

Original issue reported on code.google.com by joel.leitch@gmail.com on 14 Sep 2008 at 7:53

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

I could use that, too. Basically what's missing is some kind of 
serializeInternal()
method that ignores type adapters registered for this Type.

Original comment by maik.sch...@gmail.com on 20 Mar 2009 at 2:17

I could use that, too. Basically what's missing is some kind of 
serializeInternal()
method that ignores type adapters registered for this Type.

Original comment by maik.sch...@gmail.com on 20 Mar 2009 at 2:17

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Original comment by inder123 on 28 Mar 2009 at 5:58

  • Added labels: Milestone-Release1.4

Original comment by inder123 on 28 Mar 2009 at 5:58

  • Added labels: Milestone-Release1.4
@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

I would absolutely love this feature as well.  See this thread for related 
discussion: 
http://groups.google.com/group/google-gson/browse_thread/thread/a87d5d47b83d0cbe

Original comment by mbur...@gmail.com on 17 Jul 2009 at 5:13

I would absolutely love this feature as well.  See this thread for related 
discussion: 
http://groups.google.com/group/google-gson/browse_thread/thread/a87d5d47b83d0cbe

Original comment by mbur...@gmail.com on 17 Jul 2009 at 5:13

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

deferred to a future release

Original comment by inder123 on 29 Sep 2009 at 9:10

  • Added labels: Milestone-Release1.5
  • Removed labels: Milestone-Release1.4
deferred to a future release

Original comment by inder123 on 29 Sep 2009 at 9:10

  • Added labels: Milestone-Release1.5
  • Removed labels: Milestone-Release1.4
@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

what about cloning the src object, serializing it using context and then do all 
the 
post-processing you want?
Didn't try this though, I'm just conjecturing....

Original comment by polaretto@gmail.com on 27 Apr 2010 at 4:04

what about cloning the src object, serializing it using context and then do all 
the 
post-processing you want?
Didn't try this though, I'm just conjecturing....

Original comment by polaretto@gmail.com on 27 Apr 2010 at 4:04

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Was this actually fixed?  I'm not sure it is in 1.5, even though it is marked 
with the 1.5 milestone.

Relying on clone() is a really bad idea.

Original comment by wendel.s...@gmail.com on 26 Oct 2010 at 4:24

Was this actually fixed?  I'm not sure it is in 1.5, even though it is marked 
with the 1.5 milestone.

Relying on clone() is a really bad idea.

Original comment by wendel.s...@gmail.com on 26 Oct 2010 at 4:24

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

I could use this feature too. I do not believed it was fixed in 1.5 or current 
trunk version.

Original comment by fedorov....@gmail.com on 31 Oct 2010 at 3:24

I could use this feature too. I do not believed it was fixed in 1.5 or current 
trunk version.

Original comment by fedorov....@gmail.com on 31 Oct 2010 at 3:24

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Original comment by inder123 on 2 Nov 2010 at 11:59

  • Removed labels: Milestone-Release1.5

Original comment by inder123 on 2 Nov 2010 at 11:59

  • Removed labels: Milestone-Release1.5
@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

I experimented with this. We might be able to use the ancestors stack as a 
hint. Whenever a serializer+object pair exists on the ancestors stack, we 
should skip that serializer when recursively asked to serialize that object. 
We'll either eventually serialize it with a lower-level serializer, or we'll 
run out and we know we've hit a circular reference.

Original comment by limpbizkit on 9 Nov 2010 at 8:03

I experimented with this. We might be able to use the ancestors stack as a 
hint. Whenever a serializer+object pair exists on the ancestors stack, we 
should skip that serializer when recursively asked to serialize that object. 
We'll either eventually serialize it with a lower-level serializer, or we'll 
run out and we know we've hit a circular reference.

Original comment by limpbizkit on 9 Nov 2010 at 8:03

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

I would think that context.defaultWriteObject() (eg similar to the java object 
serialization mechanism) would make a lot of sense.

Original comment by swall...@gmail.com on 21 Jan 2011 at 4:58

I would think that context.defaultWriteObject() (eg similar to the java object 
serialization mechanism) would make a lot of sense.

Original comment by swall...@gmail.com on 21 Jan 2011 at 4:58

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

You can do this in Gson 2.1 with TypeAdapterFactory and Gson.getNextAdapter.

Original comment by limpbizkit on 29 Dec 2011 at 5:37

  • Changed state: WontFix
You can do this in Gson 2.1 with TypeAdapterFactory and Gson.getNextAdapter.

Original comment by limpbizkit on 29 Dec 2011 at 5:37

  • Changed state: WontFix
@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Issue 44 has been merged into this issue.

Original comment by limpbizkit on 29 Dec 2011 at 5:50

Issue 44 has been merged into this issue.

Original comment by limpbizkit on 29 Dec 2011 at 5:50

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Does a Gson.getNextAdapter mechanism solve the following issue?

class Obj
...

class A extends Obj
   Obj subobject;
...

Now assume we have registered:

public class ObjSerializer implements JsonSerializer<Obj> {
  public JsonElement serialize(Obj obj, Type t, JSC jsc) {
    JsonObject json = new JsonObject();
    json.addProperty("name", obj.getClass());
    json.add("value", jsc.serialize(obj, t));
    return json;
  }
}

What "should" happen is that:

A a = new A();
...
gson.toJson(a);

should have a call sequence that looks like:

ObjSerializer.serialize(a, ...)
getNextAdapter().serialize(a, ...)
ObjSerializer.serialize(subobject, ...)
getNextAdapter().serialize(subobject, ...)

That is, can we both avoid infinite recursion while also respecting nested 
registered objects' serialize mechanisms? 
http://code.google.com/p/google-gson/issues/detail?id=43#c9 seems like it 
solves this problem, but 
http://code.google.com/p/google-gson/issues/detail?id=43#c11 doesn't seem to.

Original comment by mint...@everlaw.com on 6 Feb 2012 at 9:27

Does a Gson.getNextAdapter mechanism solve the following issue?

class Obj
...

class A extends Obj
   Obj subobject;
...

Now assume we have registered:

public class ObjSerializer implements JsonSerializer<Obj> {
  public JsonElement serialize(Obj obj, Type t, JSC jsc) {
    JsonObject json = new JsonObject();
    json.addProperty("name", obj.getClass());
    json.add("value", jsc.serialize(obj, t));
    return json;
  }
}

What "should" happen is that:

A a = new A();
...
gson.toJson(a);

should have a call sequence that looks like:

ObjSerializer.serialize(a, ...)
getNextAdapter().serialize(a, ...)
ObjSerializer.serialize(subobject, ...)
getNextAdapter().serialize(subobject, ...)

That is, can we both avoid infinite recursion while also respecting nested 
registered objects' serialize mechanisms? 
http://code.google.com/p/google-gson/issues/detail?id=43#c9 seems like it 
solves this problem, but 
http://code.google.com/p/google-gson/issues/detail?id=43#c11 doesn't seem to.

Original comment by mint...@everlaw.com on 6 Feb 2012 at 9:27

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Yeah, you want getNextAdapter. That API was hidden in Gson 2.1 because we 
weren't sure that name was right, but its there in SVN. I'll write up an 
example and post it here...

Original comment by jessewil...@google.com on 7 Feb 2012 at 3:21

Yeah, you want getNextAdapter. That API was hidden in Gson 2.1 because we 
weren't sure that name was right, but its there in SVN. I'll write up an 
example and post it here...

Original comment by jessewil...@google.com on 7 Feb 2012 at 3:21

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Here's a big example that demonstrates all of the moving parts of 
getNextAdapter. Drink maps to 'Obj' and MixedDrink maps to 'A' in your model. 
Note that we're using the new streaming TypeAdapter API and not the tree-based 
JsonSerializer/JsonDeserializer API. Only the new streaming API gives you 
access to the next type adapter in the chain.


package com.google.gson;

import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;

public class GetNextAdapterExample {

    static class Drink {
        protected final String name;

        Drink(String name) {
            this.name = name;
        }

        @Override public String toString() {
            return name;
        }
    }

    static class MixedDrink extends Drink {
        private final Drink mix;
        private final String alcohol;

        MixedDrink(String name, Drink mix, String alcohol) {
            super(name);
            this.mix = mix;
            this.alcohol = alcohol;
        }

        @Override public String toString() {
            return name + " (" + mix + "+" + alcohol + ")";
        }
    }

    public static void main(String[] args) {
        Drink orangeJuice = new Drink("Orange Juice");
        MixedDrink screwdriver = new MixedDrink("Screwdriver", orangeJuice, "Vodka");

        Gson gson = new GsonBuilder()
                .registerTypeAdapterFactory(new DrinkTypeAdapterFactory())
                .create();

        // exercise toJson
        System.out.println(gson.toJson(orangeJuice));
        System.out.println(gson.toJson(screwdriver));

        // exercise fromJson
        String s = "{'name':'Orange Juice','virgin':true}";
        String t = "{'mix':{'name':'Orange Juice','virgin':true},'alcohol':'Vodka','name':'Screwdriver'}";
        System.out.println(gson.fromJson(s, Drink.class));
        System.out.println(gson.fromJson(t, Drink.class));
    }

    static class DrinkTypeAdapterFactory implements TypeAdapterFactory {
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (!Drink.class.isAssignableFrom(type.getRawType())) {
                return null; // this class only serializes 'Drink' and its subtypes
            }

            /*
             * Lookup type adapters to do the actual work. We use getNextAdapter
             * to avoid getting 'this' on the types that this factory supports.
             */
            final TypeAdapter<Drink> drinkAdapter
                    = gson.getNextAdapter(this, TypeToken.get(Drink.class));
            final TypeAdapter<MixedDrink> mixedDrinkAdapter
                    = gson.getNextAdapter(this, TypeToken.get(MixedDrink.class));

            /*
             * The JsonElement type adapter is always handy when we want to
             * tweak what our delegate type adapter created.
             */
            final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

            /**
             * Now that we have some helpers, create the tweaked type adapter.
             */
            TypeAdapter<Drink> result = new TypeAdapter<Drink>() {
                @Override public void write(JsonWriter out, Drink value) throws IOException {
                    if (value instanceof MixedDrink) {
                        // write mixed drinks out normally
                        mixedDrinkAdapter.write(out, (MixedDrink) value);
                    } else {
                        /*
                         * Always add a 'virgin' attribute on non-mixed drinks.
                         * This takes three steps:
                         *  1. Get the delegate to serialize to a JsonObject,
                         *  2. Add our extra property to that JsonObject.
                         *  3. Serialize that to the stream.
                         */
                        JsonObject object = drinkAdapter.toJsonTree(value).getAsJsonObject();
                        object.add("virgin", new JsonPrimitive(true));
                        elementAdapter.write(out, object);
                    }
                }

                @Override public Drink read(JsonReader in) throws IOException {
                    /*
                     * Use the appropriate type adapter based on the contents
                     * of the stream.
                     */
                    JsonObject object = elementAdapter.read(in).getAsJsonObject();
                    if (object.has("alcohol")) {
                        return mixedDrinkAdapter.fromJsonTree(object);
                    } else {
                        return drinkAdapter.fromJsonTree(object);
                    }
                }
            }.nullSafe(); // so we don't have to check for null on the stream

            return (TypeAdapter<T>) result;
        }
    }
}

Original comment by jessewil...@google.com on 7 Feb 2012 at 4:55

Here's a big example that demonstrates all of the moving parts of 
getNextAdapter. Drink maps to 'Obj' and MixedDrink maps to 'A' in your model. 
Note that we're using the new streaming TypeAdapter API and not the tree-based 
JsonSerializer/JsonDeserializer API. Only the new streaming API gives you 
access to the next type adapter in the chain.


package com.google.gson;

import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;

public class GetNextAdapterExample {

    static class Drink {
        protected final String name;

        Drink(String name) {
            this.name = name;
        }

        @Override public String toString() {
            return name;
        }
    }

    static class MixedDrink extends Drink {
        private final Drink mix;
        private final String alcohol;

        MixedDrink(String name, Drink mix, String alcohol) {
            super(name);
            this.mix = mix;
            this.alcohol = alcohol;
        }

        @Override public String toString() {
            return name + " (" + mix + "+" + alcohol + ")";
        }
    }

    public static void main(String[] args) {
        Drink orangeJuice = new Drink("Orange Juice");
        MixedDrink screwdriver = new MixedDrink("Screwdriver", orangeJuice, "Vodka");

        Gson gson = new GsonBuilder()
                .registerTypeAdapterFactory(new DrinkTypeAdapterFactory())
                .create();

        // exercise toJson
        System.out.println(gson.toJson(orangeJuice));
        System.out.println(gson.toJson(screwdriver));

        // exercise fromJson
        String s = "{'name':'Orange Juice','virgin':true}";
        String t = "{'mix':{'name':'Orange Juice','virgin':true},'alcohol':'Vodka','name':'Screwdriver'}";
        System.out.println(gson.fromJson(s, Drink.class));
        System.out.println(gson.fromJson(t, Drink.class));
    }

    static class DrinkTypeAdapterFactory implements TypeAdapterFactory {
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (!Drink.class.isAssignableFrom(type.getRawType())) {
                return null; // this class only serializes 'Drink' and its subtypes
            }

            /*
             * Lookup type adapters to do the actual work. We use getNextAdapter
             * to avoid getting 'this' on the types that this factory supports.
             */
            final TypeAdapter<Drink> drinkAdapter
                    = gson.getNextAdapter(this, TypeToken.get(Drink.class));
            final TypeAdapter<MixedDrink> mixedDrinkAdapter
                    = gson.getNextAdapter(this, TypeToken.get(MixedDrink.class));

            /*
             * The JsonElement type adapter is always handy when we want to
             * tweak what our delegate type adapter created.
             */
            final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

            /**
             * Now that we have some helpers, create the tweaked type adapter.
             */
            TypeAdapter<Drink> result = new TypeAdapter<Drink>() {
                @Override public void write(JsonWriter out, Drink value) throws IOException {
                    if (value instanceof MixedDrink) {
                        // write mixed drinks out normally
                        mixedDrinkAdapter.write(out, (MixedDrink) value);
                    } else {
                        /*
                         * Always add a 'virgin' attribute on non-mixed drinks.
                         * This takes three steps:
                         *  1. Get the delegate to serialize to a JsonObject,
                         *  2. Add our extra property to that JsonObject.
                         *  3. Serialize that to the stream.
                         */
                        JsonObject object = drinkAdapter.toJsonTree(value).getAsJsonObject();
                        object.add("virgin", new JsonPrimitive(true));
                        elementAdapter.write(out, object);
                    }
                }

                @Override public Drink read(JsonReader in) throws IOException {
                    /*
                     * Use the appropriate type adapter based on the contents
                     * of the stream.
                     */
                    JsonObject object = elementAdapter.read(in).getAsJsonObject();
                    if (object.has("alcohol")) {
                        return mixedDrinkAdapter.fromJsonTree(object);
                    } else {
                        return drinkAdapter.fromJsonTree(object);
                    }
                }
            }.nullSafe(); // so we don't have to check for null on the stream

            return (TypeAdapter<T>) result;
        }
    }
}

Original comment by jessewil...@google.com on 7 Feb 2012 at 4:55

@GoogleCodeExporter

This comment has been minimized.

Show comment Hide comment
@GoogleCodeExporter

GoogleCodeExporter Mar 19, 2015

Two notes about the example from the previous comment:

1) You'll need at least GSon V2.2.
2) gson.getNextAdapter was renamed to gson.getDelegateAdapter

Original comment by seble...@gmail.com on 26 Nov 2012 at 3:07

Two notes about the example from the previous comment:

1) You'll need at least GSon V2.2.
2) gson.getNextAdapter was renamed to gson.getDelegateAdapter

Original comment by seble...@gmail.com on 26 Nov 2012 at 3:07

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment