Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make Gson behavior depth-dependent

Is there a sane way of making Gson handling nested objects differently from those at the top level? Things to be serialized are entities and they all have an id. A nested entity should be replaced by its id (to shorten the output and possibly fight endless recursion).

Let's say there's a

@AllArgsConstructor class User {
    int id;
    String name;
    User parent;
}

and I execute

User grampa = new User(3, "Grampa", null);
User homer = new User(2, "Homer", grampa);
User bart = new User(1, "Bart", homer);

What I get when serializing bart is

{
    id: 1,
    name: "Bart",
    father: {
        id: 2,
        name: "Homer",
        father: {
            id: 3,
            name: "Grampa"
        }
    }
}

which is much more than I need. I actually never want to serialize nested entities, their ids are good enough.

Using a global thread-local variable hack and a TypeAdapterFactory I can get

{
    id: 1,
    name: "Bart",
    father: 2,
}

which is nearly what I want. I'd prefer fatherId to father and I'd surely prefer something less hacky than a global variable.

I don't want to create a DAO, scan the object reflexively or alike (then I could do the whole serialization myself, couldn't I?).

like image 738
maaartinus Avatar asked Mar 11 '26 09:03

maaartinus


1 Answers

The Gson documentation I think points the way to a "saner" approach. Create a TypeAdapter factory which encapsulates state and returns an inner class TypeAdapter instance referencing that state, something like this:

public class UserTypeAdapterFactory {
    private Set<Integer> serializedUsers = new HashSet<>();

    public JsonSerializer<User> getTypeAdapter() {
        return (user, type, context) -> {
            JsonObject el = new JsonObject();
            el.addProperty("id", user.getId());
            el.addProperty("name", user.getName());

            if(user.getFather() != null) {
                JsonArray els = new JsonArray();
                int fatherId = user.getFather().getId();
                if(!serializedUsers.contains(fatherId)) {
                    JsonElement father = context.serialize(user.getFather());
                    if (father.isJsonArray()) {
                        els.addAll(father.getAsJsonArray());
                    } else {
                        els.add(father);
                    }
                    serializedUsers.add(fatherId);
                }
                el.addProperty("fatherId", fatherId);
                els.add(el);
                return els;
            } else {
                return el;
            }
        };
    }
}

Gson gson = new GsonBuilder()
    .registerTypeAdapter(
        User.class, 
        new UserTypeAdapterFactory().getTypeAdapter())
    .build();
System.out.println(gson.toJson(
    new User(1, "Bart", new User(2, "Homer", new User(3, "Grampa", null)))));

Which should print:

[{"id":3,"name":"Grampa"},{"id":2,"name":"Homer","fatherId":3},{"id":1,"name":"Bart","fatherId":2}]
like image 157
sh0rug0ru Avatar answered Mar 13 '26 01:03

sh0rug0ru



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!