Skip to content

Commit

Permalink
Support for @JsonDeserialize(builder=..) (#527)
Browse files Browse the repository at this point in the history
Adds support for @JsonDeserialize(builder=..) and @JSONPojoBuilder annotations in Jackson databind
  • Loading branch information
graemerocher authored Aug 4, 2023
1 parent 28b81cf commit b724d0a
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 75 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
projectVersion=2.1.2-SNAPSHOT
projectVersion=2.2.0-SNAPSHOT
projectGroup=io.micronaut.serde

jsonbApi=https://jakarta.ee/specifications/jsonb/2.0/apidocs/jakarta/json/bind/annotation
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[versions]
micronaut = "4.0.1"
micronaut-platform = "4.0.0-RC1"
micronaut = "4.1.0"
micronaut-platform = "4.0.0"
micronaut-docs = "2.0.0"
micronaut-gradle-plugin = "4.0.1"
micronaut-test = "4.0.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.micronaut.serde.jackson.builder

import io.micronaut.serde.ObjectMapper
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Specification

@MicronautTest
class BuilderSpec extends Specification {
@Inject ObjectMapper objectMapper

void "test serialize/deserialize builder"() {
given:
def json = '{"name":"Fred","age":30}'

when:
def value = objectMapper.readValue(json, TestBuildMe)

then:
value.name == 'Fred'
value.age == 30

when:
def result = objectMapper.writeValueAsString(value)

then:
result == json
}

void "test serialize/deserialize builder with @JsonPOJOBuilder"() {
given:
def json = '{"name":"Fred","age":30}'

when:
def value = objectMapper.readValue(json, TestBuildMe2)

then:
value.name == 'Fred'
value.age == 30

when:
def result = objectMapper.writeValueAsString(value)

then:
result == json
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.micronaut.serde.jackson.builder;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.micronaut.core.annotation.Introspected;

@JsonDeserialize(
builder = TestBuildMe.Builder.class
)
public class TestBuildMe {
private final String name;
private final int age;

private TestBuildMe(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public static final class Builder {
private String name;
private int age;

public Builder name(String name) {
this.name = name;
return this;
}

public Builder age(int age) {
this.age = age;
return this;
}

public TestBuildMe build() {
return new TestBuildMe(
name,
age
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.micronaut.serde.jackson.builder;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

@JsonDeserialize(
builder = TestBuildMe2.Builder.class
)
@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "test")
public class TestBuildMe2 {
private final String name;
private final int age;

private TestBuildMe2(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public static final class Builder {
private String name;
private int age;

public Builder testName(String name) {
this.name = name;
return this;
}

public Builder testAge(int age) {
this.age = age;
return this;
}

public TestBuildMe2 create() {
return new TestBuildMe2(
name,
age
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import io.micronaut.core.annotation.AccessorsStyle;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.AnnotationValueBuilder;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.serde.config.annotation.SerdeConfig;
import io.micronaut.serde.processor.jackson.ValidatingAnnotationMapper;
Expand All @@ -42,12 +44,31 @@ protected List<AnnotationValue<?>> mapValid(AnnotationValue<Annotation> annotati
.build()
);
}
AnnotationClassValue<?> builderClass = annotation.annotationClassValue("builder").orElse(null);
if (builderClass != null) {
AnnotationValueBuilder<Introspected.IntrospectionBuilder> builderDef = AnnotationValue.builder(Introspected.IntrospectionBuilder.class);
builderDef.member("builderClass", builderClass);
visitorContext.getClassElement(builderClass.getName()).ifPresent(t -> {
AnnotationValue<Annotation> jsonPojoAnn = t.getAnnotation("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder");
if (jsonPojoAnn != null) {
jsonPojoAnn.stringValue("buildMethodName").ifPresent(n -> builderDef.member("creatorMethod", n));
jsonPojoAnn.stringValue("withPrefix").ifPresent(n ->
builderDef.member("accessorStyle", AnnotationValue.builder(AccessorsStyle.class).member("writePrefixes", n).build())
);
}
});
annotations.add(
AnnotationValue.builder(Introspected.class)
.member("builder", builderDef.build())
.build()
);
}
return annotations;
}

@Override
protected Set<String> getSupportedMemberNames() {
return Collections.singleton("as");
return Set.of("as", "builder");
}

@Override
Expand Down
Loading

0 comments on commit b724d0a

Please sign in to comment.