Ondra Žižka
Ondra Žižka

Reputation: 46904

GSON: how to prevent StackOverflowError while keeping circular references?

I have a structure with circular references. And for debug purposes, I want to dump it. Basically as any format, but I chose JSON.

Since it can be any class, I chose GSON which doesn't needs JAXB annotations.

But GSON hits the circular references and recurses until StackOverflowError.

How can I limit GSON to

Related: Gson.toJson gives StackOverFlowError, how to get proper json in this case? (public static class)

Upvotes: 12

Views: 10413

Answers (2)

Julio
Julio

Reputation: 446

I know this question has a few years now, but I'd like to contribute with my solution. Although @fdreger's answer is completely valid in case you want to exclude a field always, it doesn't work if you want to exclude it just in certain cases, avoiding this way the recursion. The way I approached the problem is:

  1. I write my own JsonSerializer. In it, I define a static variable to control de number of times an object of this same class is serialize and depending on the value, the object can be serialized or not.

     import com.fasterxml.jackson.core.JsonGenerator;
     import com.fasterxml.jackson.core.JsonProcessingException;
     import com.fasterxml.jackson.databind.JsonSerializer;
     import com.fasterxml.jackson.databind.SerializerProvider;
     import java.io.IOException;
    
     public class UserJsonSerializer extends JsonSerializer<User> {
    
         private static final ThreadLocal<Integer> depth = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 0;
            }
         };
    
         @Override
         public void serialize(User user, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
            // Here, we limit the number of instances to return. In this case, just 1.
    
            depth.set(depth.get() + 1);
            if(depth.get() >= 1) {
                generator.writeNull();
            } else {
                generator.writeObject(user);
            }
         }
    
         public static void clear() {
            depth.remove();
         }
    }
    
  2. Bind the UserJsonSerializer to the class you want to control

    public class SomeClass implements Serializable {
        @JsonSerialize(using = UserJsonSerializer.class)
        private User user;
    
       //...others fields...
    }
    
  3. Don't forget to call UserJsonSerializer#clear() method to reinitialize the counter everytime you're going to parse a new entity.

I hope this helps.

Upvotes: 0

fdreger
fdreger

Reputation: 12505

Simply make the fields transient (as in private transient int field = 4;). GSON understands that.

Edit

No need for a built-in annotation; Gson lets you plug in your own strategies for excluding fields and classes. They cannot be based on a path or nesting level, but annotations and names are fine.

If I wanted to skip fields that are named "lastName" on class "my.model.Person", I could write an exclusion strategy like this:

class MyExclusionStrategy implements ExclusionStrategy {

    public boolean shouldSkipField(FieldAttributes fa) {                
        String className = fa.getDeclaringClass().getName();
        String fieldName = fa.getName();
        return 
            className.equals("my.model.Person")
                && fieldName.equals("lastName");
    }

    @Override
    public boolean shouldSkipClass(Class<?> type) {
        // never skips any class
        return false;
    }
}

I could also make my own annotation:

@Retention(RetentionPolicy.RUNTIME)
public @interface GsonRepellent {

}

And rewrite the shouldSkipField method as:

public boolean shouldSkipField(FieldAttributes fa) {
    return fa.getAnnotation(GsonRepellent.class) != null;
}

This would enable me to do things like:

public class Person {
    @GsonRepellent
    private String lastName = "Troscianko";

    // ...

To use a custom ExclusionStrategy, build Gson object using the builder:

Gson g = new GsonBuilder()
       .setExclusionStrategies(new MyOwnExclusionStrategy())
       .create();

Upvotes: 25

Related Questions