question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support specifying method for instantiating builders in JsonDeserialize

See original GitHub issue

I request adding a parameter such as builderMethod to JsonDeserialize (e.g. @JsonDeserialize(builderMethod = "builder") ) to facilitate a way of specifying a method to instantiate a builder instance.

Example use case When using the Lombok SuperBuilder annotation it results in the creation two builder inner classes, one public and the other private which subclasses the public; it is the private class that must be instantiated. Currently when using the SuperBuilder annotation it is necessary to declare the private inner class (which will be completed during the annotation processing stage) to set its access modifier to package private so that Jackson can instantiated it.

An example original source file and the annotation processed result follows:

Original file

@JsonDeserialize(builder = LegalDocument.LegalDocumentBuilderImpl.class)
@Getter
@SuperBuilder(toBuilder = true)
public final class LegalDocument {

    private final String code;
    private final String fileName;
    private final String description;

   // This should not be necessary
    static final class LegalDocumentBuilderImpl
            extends LegalDocument.LegalDocumentBuilder<
            LegalDocument, LegalDocumentBuilderImpl> {
    }

}

After annotation processing

@JsonDeserialize(
    builder = LegalDocument.LegalDocumentBuilderImpl.class
)
public final class LegalDocument {
    private final String code;
    private final String fileName;
    private final String description;

    @Generated
    protected LegalDocument(LegalDocument.LegalDocumentBuilder<?, ?> b) {
        this.code = b.code;
        this.fileName = b.fileName;
        this.description = b.description;
    }

    // I would like to be able reference this method in the JsonDerserialize annotation
    @Generated
    public static LegalDocument.LegalDocumentBuilder<?, ?> builder() {
        return new LegalDocument.LegalDocumentBuilderImpl();
    }

    @Generated
    public LegalDocument.LegalDocumentBuilder<?, ?> toBuilder() {
        return (new LegalDocument.LegalDocumentBuilderImpl()).$fillValuesFrom(this);
    }

    @Generated
    public String getCode() {
        return this.code;
    }

    @Generated
    public String getFileName() {
        return this.fileName;
    }

    @Generated
    public String getDescription() {
        return this.description;
    }

    @Generated
    public abstract static class LegalDocumentBuilder<C extends LegalDocument, B extends LegalDocument.LegalDocumentBuilder<C, B>> {
        @Generated
        private String code;
        @Generated
        private String fileName;
        @Generated
        private String description;

        public LegalDocumentBuilder() {
        }

        @Generated
        protected B $fillValuesFrom(C instance) {
            $fillValuesFromInstanceIntoBuilder(instance, this);
            return this.self();
        }

        @Generated
        private static void $fillValuesFromInstanceIntoBuilder(LegalDocument instance, LegalDocument.LegalDocumentBuilder<?, ?> b) {
            b.code(instance.code);
            b.fileName(instance.fileName);
            b.description(instance.description);
        }

        @Generated
        protected abstract B self();

        @Generated
        public abstract C build();

        @Generated
        public B code(String code) {
            this.code = code;
            return this.self();
        }

        @Generated
        public B fileName(String fileName) {
            this.fileName = fileName;
            return this.self();
        }

        @Generated
        public B description(String description) {
            this.description = description;
            return this.self();
        }

        @Generated
        public String toString() {
            return "LegalDocument.LegalDocumentBuilder(code=" + this.code + ", fileName=" + this.fileName + ", description=" + this.description + ")";
        }
    }

    static final class LegalDocumentBuilderImpl extends LegalDocument.LegalDocumentBuilder<LegalDocument, LegalDocument.LegalDocumentBuilderImpl> {
        @Generated
        private LegalDocumentBuilderImpl() {
        }

        @Generated
        protected LegalDocument.LegalDocumentBuilderImpl self() {
            return this;
        }

        @Generated
        public LegalDocument build() {
            return new LegalDocument(this);
        }
    }
}

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:4
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
cowtowncodercommented, Feb 10, 2020

Ah, interesting. My interest in test was more wrt actual dynamic access, but then again with CI system it could catch changes to ability compile too (should warnings become errors).

Either way, this is useful information as it makes the case for addition of method stronger. I’ll have to see if this would be doable for 2.11, given that it requires annotation addition (but to annotation that is in databind, one of few)

1reaction
janriekecommented, Feb 10, 2020

Here is the relevant part of the JLS (section 6.6.1.):

Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level type (§7.6) that encloses the declaration of the member or constructor.

The important part is “within the body of the top level type”. As the annotation is placed outside the body of the outer class, javac is correct to complain that the nested private class is not accessible. If this compiles in javac 8 (I can’t verify that right now), I’m quite sure the behavior of javac 8 would be incorrect (and that javac 9 complains is more likely a bugfix than a result of the module system).

The test you introduced in b448a2a only compiles because you have a top-level type around the nested class that has the annotation. Thus, the annotation is within the body of the top-level type. IMO the JLS is not very consistent at this point (my guess is that this definition existed before generics and annotations were introduced). However, this is not the typical case: Most builders are for top-level types, not nested types.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jackson Deserialization and the Builder Pattern
Tutorial that shows how to use the builder pattern with Jackson for ... our class with @JsonDeserialize and specify our Builder class there:....
Read more >
JsonPOJOBuilder (jackson-databind 2.13.0 API) - FasterXML
Annotation used to configure details of a Builder class: instances of which are used as Builders for deserialized POJO values, instead of POJOs...
Read more >
How to instantiate beans in custom way with Jackson?
Some things that may help... First, you can use @JsonCreator to define alternate constructor to use (all arguments must be annotated with ...
Read more >
More Jackson Annotations - Baeldung
Using the value element, we can specify any strategy, ... name of the no-arg method used to instantiate the expected bean after binding...
Read more >
DeserializationContext (jackson-databind 2.9.9 API) - Javadoc.io
Helper method for constructing instantiation exception for specified type, to indicate that instantiation failed due to missing instantiator (creator; ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found