简体   繁体   中英

Gson add field during serialization

I can't find a simple way to add a custom field during serialization in Gson and I was hoping someone else may be able to help.

Here is a sample class to show my issue:

public class A {
  String id;
  String name;
  ...
}

When I serialize class AI would like to return something like:

{ "id":"123", "name":"John Doe", "url_to_user":"http://www.example.com/123" }

where url_to_user is not stored in my instance of class A, but can be generated with data in the instance of class A.

Is there a simple way of doing this? I would prefer to avoid writing an entire serializer just to add one field.

Use Gson.toJsonTree to get a JsonElement , with which you can interact dynamically.

A a = getYourAInstanceHere();
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(a);
jsonElement.getAsJsonObject().addProperty("url_to_user", url);
return gson.toJson(jsonElement);

Well, the top rated answer is quite a quick one and not essentially bad when you are lacking much time but here is the problem: There is no proper separation of concern

You are modifying the serialized JSON at the same place where you are writing your business logic. You should be doing all the serialization inside of a TypeAdapter or a JsonSerializer .

How can we maintain a proper separation of concern?

The answer wraps around a bit of additional complexity but the architecture demands it. Here we go(taken from my other answer ):

First, we would be using a custom serializer for the type. Second, we would have to create a copy constructor inside the base class and a wrapper subclass as follows:

Note: The custom serializer might seem like an overkill but trust me, it pays off in long run for maintainability. .

// Lets say the base class is named Cat
public class Cat {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }
    // COPY CONSTRUCTOR
    public Cat(Cat cat) {
        this.name = cat.name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}



    // The wrapper subclass for serialization
public class CatWrapper extends Cat{


    public CatWrapper(String name) {
        super(name);
    }

    public CatWrapper(Cat cat) {
        super(cat);
    }
}

And the serializer for the type Cat :

public class CatSerializer implements JsonSerializer<Cat> {

    @Override
    public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {

        // Essentially the same as the type Cat
        JsonElement catWrapped = context.serialize(new CatWrapper(src));

        // Here, we can customize the generated JSON from the wrapper as we want.
        // We can add a field, remove a field, etc.

        // The main logic from the top rated answer now here instead of *spilling* around(Kindly ignore the cat having a url for the sake of example)
        return catWrapped.getAsJsonObject().addProperty("url_to_user", url);
    }
}

So, why a copy constructor?

Well, once you define the copy constructor, no matter how much the base class changes, your wrapper will continue with the same role. Secondly, if we don't define a copy constructor and simply subclass the base class then we would have to "talk" in terms of the extended class, ie, CatWrapper . It is quite possible that your components talk in terms of the base class and not the wrapper type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM