Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem with JavaType.toString() for recursive (self-referential) types #1301

Closed
voidmain opened this issue Jul 16, 2016 · 8 comments
Closed
Milestone

Comments

@voidmain
Copy link

I'm getting a StackOverflowError when I try to use this class with Jackson:

public class DataDefinition extends HashMap<String, DataDefinition> {
  public DataDefinition definition;
  public DataDefinition elements;
  public String regex;
  public boolean required;
  public String type;
}

Here's a snippet of the stack trace I get:

    at com.fasterxml.jackson.databind.type.ResolvedRecursiveType.toString(ResolvedRecursiveType.java:90)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.fasterxml.jackson.databind.type.MapType.toString(MapType.java:161)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.fasterxml.jackson.databind.type.ResolvedRecursiveType.toString(ResolvedRecursiveType.java:90)
    at java.lang.String.valueOf(String.java:2994)

I did some debugging and found that this looks like the location where the stack track starts:

    public StdValueInstantiator(DeserializationConfig config, JavaType valueType) {
        _valueTypeDesc = (valueType == null) ? "UNKNOWN TYPE" : valueType.toString();
        _valueClass = (valueType == null) ? Object.class : valueType.getRawClass();
    }

It's line 87 in StdValueInstantiator. That looks like it is calling toString() on the on the type and then it goes into an infinite recursion state. I'm not sure why the toString() call is necessary, but it seems like all of this is for debugging and errors and pre-calculating the string is preventing Jackson from working at all even if there isn't an error.

WORK-AROUND: In case anyone looks at this issue and needs a work around, you can use the @JsonAnySetter to fix this issue. Here's the code:

public class DataDefinition {
  public DataDefinition definition;
  public DataDefinition elements;
  @JsonAnySetter
  public Map<String, DataDefinition> properties = new HashMap<>();
  public String regex;
  public boolean required;
  public String type;
}

I also think that this can likely be fixed with code like this:

com.fasterxml.jackson.databind.type.MapType.java#toString()

    public String toString()
    {
        if ((_keyType instance ResolvedRecursiveType && ((ResolvedRecursiveType) _keyType).getSelfReferencedType() == this) || (_valueType instance ResolvedRecursiveType && ((ResolvedRecursiveType) _valueType).getSelfReferencedType() == this)) {
            return "[map type; class "+_class.getName()+", "+_keyType+" -> self]";
        }
        return "[map type; class "+_class.getName()+", "+_keyType+" -> "+_valueType+"]";
    }
@cowtowncoder
Copy link
Member

Hmmh. This is tricky because it is truly recursive definition, and there is no clean way to keep track of reference track during toString(). Obviously code should not fail that way, so need to figure out a better way to make this work.
Plus in general toString() can't fail that way either. Just need to think of the cleanest way to resolve the problem.

@cowtowncoder cowtowncoder changed the title Problem with generic binding where a generic parameter is the current type Problem with JavaType.toString() for recursive (self-referential) types Jul 18, 2016
@cowtowncoder cowtowncoder added this to the 2.7.6 milestone Jul 18, 2016
@cowtowncoder
Copy link
Member

Thank you again -- I was able to fix it relatively easily since this affects self-referential ResolvedRecursiveType instances. Fix is in for 2.7.6 and 2.8.1 (since these are open branches).

@cowtowncoder
Copy link
Member

Also: for what that is worth, test fails on 2.6, but for other reasons; recursive type resolution itself fails.
Type resolution was rewritten in 2.7 to address this case (amongst others), so there is no way to backport fix there, even if branch was open.

@newkek
Copy link

newkek commented Jun 14, 2017

Hello, I am using jackson-databind 2.8.8, and have a class with a similar definition (extending a Map, where the values are are of the type of the same class). It seems like I am still facing this exact recursion issue.

Here's the definition of the class:

public class Tree<T> extends HashMap<T, Tree<T>> implements Serializable {
....
}

Now, when executing the following code:

        ObjectMapper om = new ObjectMapper();
        final TypeResolverBuilder<?> typer = new StdTypeResolverBuilder()
                .init(JsonTypeInfo.Id.CLASS, null)
                .inclusion(JsonTypeInfo.As.PROPERTY)
                .typeProperty(GraphSONTokens.CLASS);
        om.setDefaultTyping(typer);

        String res = om.writeValueAsString(t);
        Object tRead = om.readValue(res, Tree.class);

        System.out.println("tRead = " + tRead);

When calling readValue() the mapper throws a StackOverflowException , here's the stacktrace:

java.lang.StackOverflowError
	at org.apache.tinkerpop.shaded.jackson.databind.type.MapLikeType.equals(MapLikeType.java:305)
	at org.apache.tinkerpop.shaded.jackson.databind.type.ResolvedRecursiveType.equals(ResolvedRecursiveType.java:110)
	at org.apache.tinkerpop.shaded.jackson.databind.type.MapLikeType.equals(MapLikeType.java:305)
	at org.apache.tinkerpop.shaded.jackson.databind.type.ResolvedRecursiveType.equals(ResolvedRecursiveType.java:110)
	at org.apache.tinkerpop.shaded.jackson.databind.type.MapLikeType.equals(MapLikeType.java:305)
	at org.apache.tinkerpop.shaded.jackson.databind.type.ResolvedRecursiveType.equals(ResolvedRecursiveType.java:110)
	at org.apache.tinkerpop.shaded.jackson.databind.type.MapLikeType.equals(MapLikeType.java:305)
	at org.apache.tinkerpop.shaded.jackson.databind.type.ResolvedRecursiveType.equals(ResolvedRecursiveType.java:110)
	at org.apache.tinkerpop.shaded.jackson.databind.type.MapLikeType.equals(MapLikeType.java:305)
[...]

Looking briefly into the code, it seems like because of the recursive definition of the class, the equals call in MapLikeType may never get out of this loop. Any idea?

@cowtowncoder
Copy link
Member

@newkek Could you please file a new issue, copying information you provided? I need a reproduction and see if and how to prevent infinite recursion. It is possible this is regression due to other changes/fixes needed to support usage of self-recursive type references.

@newkek
Copy link

newkek commented Jun 14, 2017

Thanks - will do right away.

@newkek
Copy link

newkek commented Jun 14, 2017

New issue: #1658.
Thanks.

@ghost
Copy link

ghost commented Jul 1, 2019

@ALL - How to solve this issue ? Does any knows the solution ?

@FasterXML FasterXML locked as resolved and limited conversation to collaborators Jul 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants