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

fix: resolve bean field/method types earlier to unwrap generic types #2101

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,17 @@ private boolean isExposedByDefault() {
* @return resolved type (if found)
*/
private Type getResolvedType(Type fieldType) {
return getResolvedType(fieldType, resolutionStack);
}

/**
* Resolve a type against the given resolution stack
*
* @param fieldType type to resolve
* @param resolutionStack stack of types for resolution
* @return resolved type (if found)
*/
private static Type getResolvedType(Type fieldType, Deque<Map<String, Type>> resolutionStack) {
Type current = TypeUtil.resolveWildcard(fieldType);

for (Map<String, Type> map : resolutionStack) {
Expand Down Expand Up @@ -571,7 +582,7 @@ private static List<MethodInfo> methods(AnnotationScannerContext context, ClassI
if (context.getConfig().sortedPropertiesEnable()) {
return currentClass.methods();
}
return currentClass.unsortedMethods();
return currentClass.methodsInDeclarationOrder();
}

private static boolean acceptMethod(MethodInfo method) {
Expand Down Expand Up @@ -785,7 +796,7 @@ private static void scanField(AnnotationScannerContext context, Map<String, Type
AnnotationTarget reference,
List<ClassInfo> descendants) {
final String propertyName = field.name();
final Type fieldType = field.type();
final Type fieldType = getResolvedType(field.type(), stack);
final ClassInfo fieldClass = context.getAugmentedIndex().getClass(fieldType);
final boolean unwrapped;
final TypeResolver resolver;
Expand Down Expand Up @@ -878,7 +889,7 @@ private static void scanMethod(AnnotationScannerContext context, Map<String, Typ
Deque<Map<String, Type>> stack,
AnnotationTarget reference,
List<ClassInfo> descendants) {
Type returnType = method.returnType();
Type returnType = getResolvedType(method.returnType(), stack);
Type propertyType = null;

if (isAccessor(context, method)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -331,43 +332,73 @@ void testJakartaJaxbElementUnwrapped() throws IOException, JSONException {
@Test
void testJacksonJsonUnwrapped() throws IOException, JSONException {
assertJsonEquals("components.schemas-jackson-jsonunwrapped.json",
JacksonJsonPerson.class, JacksonJsonPersonWithPrefixedAddress.class,
JacksonJsonPersonWithSuffixedAddress.class, JacksonJsonAddress.class);
JacksonJsonUnwrapped.JacksonJsonPerson.class,
JacksonJsonUnwrapped.JacksonJsonPersonWithPrefixedAddress.class,
JacksonJsonUnwrapped.JacksonJsonPersonWithSuffixedAddress.class,
JacksonJsonUnwrapped.JacksonJsonAddress.class,
JacksonJsonUnwrapped.TimestampedEntity.class,
JacksonJsonUnwrapped.Alternative.class,
JacksonJsonUnwrapped.Greeting.class,
JacksonJsonUnwrapped.LanguageAlternatives.class);
}

@Schema
static class JacksonJsonPerson {
protected String name;
@JsonUnwrapped
protected JacksonJsonAddress address;
static class JacksonJsonUnwrapped {
@Schema
static class JacksonJsonPerson {
protected String name;
@JsonUnwrapped
protected JacksonJsonAddress address;
protected TimestampedEntity<Greeting<LanguageAlternatives>> greeting;

@Schema(description = "Ignored since address is unwrapped")
public JacksonJsonAddress getAddress() {
return address;
@Schema(description = "Ignored since address is unwrapped")
public JacksonJsonAddress getAddress() {
return address;
}
}
}

@Schema
static class JacksonJsonPersonWithPrefixedAddress {
protected String name;
@JsonUnwrapped(prefix = "addr-")
protected JacksonJsonAddress address;
}
@Schema
static class JacksonJsonPersonWithPrefixedAddress {
protected String name;
@JsonUnwrapped(prefix = "addr-")
protected JacksonJsonAddress address;
}

@Schema
static class JacksonJsonPersonWithSuffixedAddress {
protected String name;
@JsonUnwrapped(suffix = "-addr")
protected JacksonJsonAddress address;
}
@Schema
static class JacksonJsonPersonWithSuffixedAddress {
protected String name;
@JsonUnwrapped(suffix = "-addr")
protected JacksonJsonAddress address;
}

@Schema
static class JacksonJsonAddress {
protected int streetNumber;
protected String streetName;
protected String city;
protected String state;
protected String postalCode;
@Schema
static class JacksonJsonAddress {
protected int streetNumber;
protected String streetName;
protected String city;
protected String state;
protected String postalCode;
}

static class TimestampedEntity<T> {
@JsonUnwrapped
T entity;
Instant timestamp;
}

interface Alternative {
}

static class Greeting<T extends Alternative> {
String message;
T alternatives;
}

static class LanguageAlternatives implements Alternative {
@Schema(required = true, example = "Hola")
String spanish;
@Schema(required = true, example = "Hallo")
String german;
}
}

/****************************************************************/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"type": "string"
},
"streetNumber": {
"format": "int32",
"type": "integer"
"type": "integer",
"format": "int32"
},
"city": {
"type": "string"
Expand All @@ -23,6 +23,9 @@
},
"name": {
"type": "string"
},
"greeting": {
"$ref": "#/components/schemas/TimestampedEntityGreetingLanguageAlternatives"
}
}
},
Expand All @@ -42,8 +45,8 @@
"type": "string"
},
"addr-streetNumber": {
"format": "int32",
"type": "integer"
"type": "integer",
"format": "int32"
},
"name": {
"type": "string"
Expand All @@ -63,8 +66,8 @@
"type": "string"
},
"streetNumber-addr": {
"format": "int32",
"type": "integer"
"type": "integer",
"format": "int32"
},
"state-addr": {
"type": "string"
Expand All @@ -77,21 +80,60 @@
"JacksonJsonAddress": {
"type": "object",
"properties": {
"city": {
"streetNumber": {
"type": "integer",
"format": "int32"
},
"streetName": {
"type": "string"
},
"postalCode": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"streetName": {
"postalCode": {
"type": "string"
}
}
},
"LanguageAlternatives": {
"type": "object",
"required": [
"spanish",
"german"
],
"properties": {
"spanish": {
"type": "string",
"examples": [
"Hola"
]
},
"german": {
"type": "string",
"examples": [
"Hallo"
]
}
}
},
"TimestampedEntityGreetingLanguageAlternatives": {
"type": "object",
"properties": {
"alternatives": {
"$ref": "#/components/schemas/LanguageAlternatives"
},
"message": {
"type": "string"
},
"streetNumber": {
"format": "int32",
"type": "integer"
"timestamp": {
"type": "string",
"format": "date-time",
"examples": [
"2022-03-10T16:15:50Z"
]
}
}
}
Expand Down
Loading