1

Background

I want to serialize an interface into a JSON object. For instance, say I have the following interface:

public interface Person {
    String getName();
    int getAge();
}

Which is implemented by the class PersonImpl:

public class PersonImpl implements Person {
    private String name;
    private int age;

    public PersonImpl(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    @Override
    public int getAge() {
        return age;
    }
}

PersonSerializer.java is used to serialize the interface using GSON:

public class PersonSerializer implements JsonSerializer<Person> {

    @Override
    public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject person = new JsonObject();
        person.addProperty("name", src.getName());
        person.addProperty("age", src.getAge());
        return person; // Breakpoint BP1
    }
}

I then use a GsonBuilder to serialize the interface:

Person person = new PersonImpl("Bob", 42);
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Person.class, new PersonSerializer());
Gson gson = builder.create();
String personAsJsonString = gson.toJson(person); // Breakpoint BP2

Issue

The problem is the Person class is being serialized as follows:

// Not what I want
{
    "person": {
        "name": "Bob",
        "age": 42
    }
}

However, I only want the data within person:

// What I want
{
    "name": "Bob",
    "age": 42
}

Troubleshooting & Debugging

At Breakpoint BP1 (noted via the comment), the String value of person is exactly what I want. However, by Breakpoint BP2, after GSON completes serialization of the interface (i.e. personAsJsonString), it is the undesired result.

How can I get GSON to serialize a custom interface without encapsulating the result in a JSON Object?

Edit (root cause found)

I completely failed to mention that I was using the Decorator Pattern, which happened to be the root cause of the issue.

public abstract class PersonDecorator implements Person {
    protected Person person;

    public PersonDecorator(Person person) {
        this.person = person;
    }

    @Override
    public String getName() {
        return person.getName();
    }

    @Override
    public int getAge() {
        return person.getAge();
    }
}

Below is an example Decorator:

public class MrPersonDecorator extends PersonDecorator {
    public MrPersonDecorator(Person person) {
        super(person);
    }

    @Override
    public String getName() {
        return "Mr. " + person.getName();
    }
}

So the issue happened when creating a Person instance via a decorator:

Person person = new MrPersonDecorator(new PersonImpl("Bob", 42));

In this case, I was not getting a reference to the interface itself, but rather the decorator, which also contains an instance of the interface along with implementing it.

5
  • I can't reproduce this issue. Which version of gson do you use? Commented May 13, 2016 at 21:36
  • @qwwdfsad 2.3.1. Which version did you use? Commented May 13, 2016 at 21:38
  • Exactly case from you question is serialized without rootname (as it should be). Either specify your question or check your code please Commented May 13, 2016 at 21:40
  • @qwwdfsad Which version of GSON did you try? Commented May 13, 2016 at 21:53
  • @qwwdfsad I found the root cause and updated the question. thank you for your help! Commented May 14, 2016 at 2:20

1 Answer 1

1

Gson doesn't provide such mechanics (note, that it's not a principal constraint, e.g. Jackson provides it via @JsonUnwrapped annotation or SerializationFeature.UNWRAP_ROOT_VALUE on mapper).

What you can do is to either provide custom serializer for adapter like this:

public class PersonDecoratorSerializer implements JsonSerializer<PersonDecorator> {

    @Override
    public JsonElement serialize(PersonDecorator src, Type typeOfSrc, JsonSerializationContext context) {
        return context.serialize(src.getOriginalPerson()); 
    }
}

To avoid violating encapsulation you can add package-private getOriginalPerson() method to PersonDecorator and create serializer as inner static class so it can access this method.

Or wrap GSON calls to extract your person value: replace gson.toJson(person) with gson.toJsonTree(person).getAsJsonObject().get("person").toString():

public static String toJson(Person person, Gson gson) {
    JsonObject object = gson.toJsonTree(person).getAsJsonObject();
    // Checks whether Person was PersonDecorator or not
    if (object.has("person")) {
      return object.get("person").toString();
    } else {
      return object.toString();
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.