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

YAML anchor UnresolvedForwardReference with builder #292

Open
almson opened this issue Oct 11, 2021 · 4 comments
Open

YAML anchor UnresolvedForwardReference with builder #292

almson opened this issue Oct 11, 2021 · 4 comments
Labels
yaml Issue related to YAML format backend

Comments

@almson
Copy link

almson commented Oct 11, 2021

YAML anchor resolution fails with UnresolvedForwardReference in the following code.

// Generated by delombok at Mon Oct 11 17:00:06 EEST 2021
package mypackage;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.util.List;
import org.junit.jupiter.api.Test;

public class JacksonYamlTest {

    static class Container {
        @JsonProperty
        List<Base> list;
    }


    @JsonTypeInfo(use = Id.NAME)
    @JsonSubTypes({@Type(name = "Derived", value = Derived.class)})
    @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class, resolver = SimpleObjectIdResolver.class)
    interface Base {
    }


    @JsonDeserialize(builder = Derived.DerivedBuilder.class)
    static class Derived implements Base {
        @JsonProperty
        String a;

        Derived(final String a) {
            this.a = a;
        }


        @JsonPOJOBuilder(withPrefix = "", buildMethodName = "build")
        public static class DerivedBuilder {
            private String a;

            DerivedBuilder() {
            }

            @JsonProperty
            public DerivedBuilder a(final String a) {
                this.a = a;
                return this;
            }

            public Derived build() {
                return new Derived(this.a);
            }
        }

        public static DerivedBuilder builder() {
            return new DerivedBuilder();
        }
    }

    @Test
    public void typedObjectIdTest() throws Exception {
        String yaml = "list:\n" + 
                "    - !Derived &id1\n" + 
                "        a: foo\n" + 
                "    - *id1";
        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        Container container = mapper.readValue(yaml, Container.class);
        System.out.println(((Derived) container.list.get(0)).a);
        System.out.println(((Derived) container.list.get(1)).a);
        
        var serialized = mapper.writeValueAsString (container);
        System.out.println (serialized);
    }
}

throws

com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: 
Unresolved forward references for: 
 at [Source: (StringReader); line: 4, column: 11]Object id [id1] (for `com.syncwords.config.JacksonYamlTest$Base`) at [Source: (StringReader); line: 4, column: 11].
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.checkUnresolvedObjectId(DefaultDeserializationContext.java:171)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4595)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)

Note that changing interface Base to static class Base resolves the problem, however I require an interface. Everything else works fine except using anchors.

@almson
Copy link
Author

almson commented Oct 11, 2021

For reference, the original lombok code is:

package mypackage;


import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.util.List;
import org.junit.jupiter.api.Test;
import lombok.Builder;
import lombok.extern.jackson.Jacksonized;

public class JacksonYamlTest {
    
    static class Container {
        @JsonProperty
        List<Base> list;
    }
    
    @JsonTypeInfo(use = Id.NAME)
    @JsonSubTypes({@Type(name="Derived", value=Derived.class)})
    @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class)
    interface Base {
        
    }
    
    @Jacksonized @Builder
    static class Derived implements Base {
        @JsonProperty
        String a;
    }
    
    @Test
    public void typedObjectIdTest() throws Exception {
        
        String yaml = "list:\n" +
                      "    - !Derived &id1\n" +
                      "        a: foo\n" +
                      "    - *id1";
        
        ObjectMapper mapper = new ObjectMapper (new YAMLFactory());
        Container container = mapper.readValue (yaml, Container.class);
        
        System.out.println (((Derived)container.list.get(0)).a);
        System.out.println (((Derived)container.list.get(1)).a);
    }
}

@cowtowncoder
Copy link
Member

And this is with a reasonably recent Jackson version? (2.12.5 or 2.13.0)

@cowtowncoder
Copy link
Member

Ah, yes. Object Identity does not work with Builders; combination is not supported.

So this is unlikely to work in near future; it does not work with JSON either.

@almson
Copy link
Author

almson commented Oct 26, 2021

@cowtowncoder Well it does work if base type is class. It only breaks with interface, which seems odd.

@cowtowncoder cowtowncoder added the yaml Issue related to YAML format backend label Nov 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
yaml Issue related to YAML format backend
Projects
None yet
Development

No branches or pull requests

2 participants