Skip to content

Commit 1721de8

Browse files
Add Type in addition to Class to map types with generic parameters (#438) (#439)
Co-authored-by: Sylvain Wallez <[email protected]>
1 parent d854c7f commit 1721de8

File tree

13 files changed

+152
-46
lines changed

13 files changed

+152
-46
lines changed

java-client/src/main/java/co/elastic/clients/ApiClient.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import co.elastic.clients.json.JsonpMapperBase;
2727

2828
import javax.annotation.Nullable;
29+
import java.lang.reflect.Type;
2930
import java.util.function.Function;
3031

3132
public abstract class ApiClient<T extends Transport, Self extends ApiClient<T, Self>> {
@@ -38,14 +39,14 @@ protected ApiClient(T transport, TransportOptions transportOptions) {
3839
this.transportOptions = transportOptions;
3940
}
4041

41-
protected <V> JsonpDeserializer<V> getDeserializer(Class<V> clazz) {
42+
protected <V> JsonpDeserializer<V> getDeserializer(Type type) {
4243
// Try the built-in deserializers first to avoid repeated lookups in the Jsonp mapper for client-defined classes
43-
JsonpDeserializer<V> result = JsonpMapperBase.findDeserializer(clazz);
44+
JsonpDeserializer<V> result = JsonpMapperBase.findDeserializer(type);
4445
if (result != null) {
4546
return result;
4647
}
4748

48-
return JsonpDeserializer.of(clazz);
49+
return JsonpDeserializer.of(type);
4950
}
5051

5152
/**

java-client/src/main/java/co/elastic/clients/json/DelegatingJsonpMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import jakarta.json.stream.JsonParser;
2525

2626
import javax.annotation.Nullable;
27+
import java.lang.reflect.Type;
2728

2829
public abstract class DelegatingJsonpMapper implements JsonpMapper {
2930

@@ -39,8 +40,8 @@ public JsonProvider jsonProvider() {
3940
}
4041

4142
@Override
42-
public <T> T deserialize(JsonParser parser, Class<T> clazz) {
43-
return mapper.deserialize(parser, clazz);
43+
public <T> T deserialize(JsonParser parser, Type type) {
44+
return mapper.deserialize(parser, type);
4445
}
4546

4647
@Override

java-client/src/main/java/co/elastic/clients/json/JsonData.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.io.InputStream;
2727
import java.io.Reader;
2828
import java.io.StringReader;
29+
import java.lang.reflect.Type;
2930
import java.util.EnumSet;
3031

3132
/**
@@ -59,11 +60,23 @@ public interface JsonData extends JsonpSerializable {
5960
*/
6061
<T> T to(Class<T> clazz);
6162

63+
/**
64+
* Converts this object to a target type. A mapper must have been provided at creation time.
65+
*
66+
* @throws IllegalStateException if no mapper was provided at creation time.
67+
*/
68+
<T> T to(Type type);
69+
6270
/**
6371
* Converts this object to a target class.
6472
*/
6573
<T> T to(Class<T> clazz, JsonpMapper mapper);
6674

75+
/**
76+
* Converts this object to a target type.
77+
*/
78+
<T> T to(Type type, JsonpMapper mapper);
79+
6780
/**
6881
* Converts this object using a deserializer. A mapper must have been provided at creation time.
6982
*

java-client/src/main/java/co/elastic/clients/json/JsonDataImpl.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.io.StringReader;
2727
import java.io.StringWriter;
28+
import java.lang.reflect.Type;
2829

2930
class JsonDataImpl implements JsonData {
3031
private final Object value;
@@ -65,19 +66,30 @@ public JsonValue toJson(JsonpMapper mapper) {
6566

6667
@Override
6768
public <T> T to(Class<T> clazz) {
69+
return to((Type)clazz, null);
70+
}
71+
72+
@Override
73+
public <T> T to(Type clazz) {
6874
return to(clazz, null);
6975
}
7076

7177
@Override
7278
public <T> T to(Class<T> clazz, JsonpMapper mapper) {
73-
if (clazz.isAssignableFrom(value.getClass())) {
74-
return (T) value;
79+
return to((Type)clazz, mapper);
80+
}
81+
82+
@Override
83+
public <T> T to(Type type, JsonpMapper mapper) {
84+
if (type instanceof Class<?> && ((Class<?>)type).isAssignableFrom(value.getClass())) {
85+
@SuppressWarnings("unchecked")
86+
T result = (T) value;
87+
return result;
7588
}
7689

7790
mapper = getMapper(mapper);
78-
7991
JsonParser parser = getParser(mapper);
80-
return mapper.deserialize(parser, clazz);
92+
return mapper.deserialize(parser, type);
8193
}
8294

8395
@Override

java-client/src/main/java/co/elastic/clients/json/JsonpDeserializer.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import jakarta.json.stream.JsonParser.Event;
2626

2727
import java.io.StringReader;
28+
import java.lang.reflect.Type;
2829
import java.util.EnumSet;
2930
import java.util.List;
3031
import java.util.Map;
@@ -88,15 +89,23 @@ default V deserialize(JsonParser parser, JsonpMapper mapper) {
8889

8990
//---------------------------------------------------------------------------------------------
9091

92+
// /**
93+
// * Creates a deserializer for a class that delegates to the mapper provided to
94+
// * {@link #deserialize(JsonParser, JsonpMapper)}.
95+
// */
96+
// static <T>JsonpDeserializer<T> of(Class<T> clazz) {
97+
// return of((Type)clazz);
98+
// }
99+
91100
/**
92-
* Creates a deserializer for a class that delegates to the mapper provided to
101+
* Creates a deserializer for a type that delegates to the mapper provided to
93102
* {@link #deserialize(JsonParser, JsonpMapper)}.
94103
*/
95-
static <T>JsonpDeserializer<T> of (Class<T> clazz) {
104+
static <T>JsonpDeserializer<T> of(Type type) {
96105
return new JsonpDeserializerBase<T>(EnumSet.allOf(JsonParser.Event.class)) {
97106
@Override
98107
public T deserialize(JsonParser parser, JsonpMapper mapper) {
99-
return mapper.deserialize(parser, clazz);
108+
return mapper.deserialize(parser, type);
100109
}
101110

102111
@Override

java-client/src/main/java/co/elastic/clients/json/JsonpMapper.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import jakarta.json.stream.JsonParser;
2525

2626
import javax.annotation.Nullable;
27+
import java.lang.reflect.Type;
2728

2829
/**
2930
* A {@code JsonpMapper} combines a JSON-P provider and object serialization/deserialization based on JSON-P events.
@@ -44,7 +45,14 @@ public interface JsonpMapper {
4445
/**
4546
* Deserialize an object, given its class.
4647
*/
47-
<T> T deserialize(JsonParser parser, Class<T> clazz);
48+
default <T> T deserialize(JsonParser parser, Class<T> clazz) {
49+
return deserialize(parser, (Type)clazz);
50+
}
51+
52+
/**
53+
* Deserialize an object, given its type.
54+
*/
55+
<T> T deserialize(JsonParser parser, Type type);
4856

4957
/**
5058
* Serialize an object.

java-client/src/main/java/co/elastic/clients/json/JsonpMapperBase.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525

2626
import javax.annotation.Nullable;
2727
import java.lang.reflect.Field;
28+
import java.lang.reflect.Type;
2829
import java.util.Collections;
2930
import java.util.HashMap;
3031
import java.util.Map;
3132

3233
public abstract class JsonpMapperBase implements JsonpMapper {
3334

3435
/** Get a serializer when none of the builtin ones are applicable */
35-
protected abstract <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz);
36+
protected abstract <T> JsonpDeserializer<T> getDefaultDeserializer(Type type);
3637

3738
private Map<String, Object> attributes;
3839

@@ -61,29 +62,39 @@ protected JsonpMapperBase addAttribute(String name, Object value) {
6162
}
6263

6364
@Override
64-
public <T> T deserialize(JsonParser parser, Class<T> clazz) {
65-
JsonpDeserializer<T> deserializer = findDeserializer(clazz);
65+
public <T> T deserialize(JsonParser parser, Type type) {
66+
JsonpDeserializer<T> deserializer = findDeserializer(type);
6667
if (deserializer != null) {
6768
return deserializer.deserialize(parser, this);
6869
}
6970

70-
return getDefaultDeserializer(clazz).deserialize(parser, this);
71+
@SuppressWarnings("unchecked")
72+
T result = (T)getDefaultDeserializer(type).deserialize(parser, this);
73+
return result;
7174
}
7275

7376
@Nullable
74-
@SuppressWarnings("unchecked")
7577
public static <T> JsonpDeserializer<T> findDeserializer(Class<T> clazz) {
76-
JsonpDeserializable annotation = clazz.getAnnotation(JsonpDeserializable.class);
77-
if (annotation != null) {
78-
try {
79-
Field field = clazz.getDeclaredField(annotation.field());
80-
return (JsonpDeserializer<T>)field.get(null);
81-
} catch (Exception e) {
82-
throw new RuntimeException("No deserializer found in '" + clazz.getName() + "." + annotation.field() + "'");
78+
return findDeserializer((Type)clazz);
79+
}
80+
81+
@Nullable
82+
@SuppressWarnings("unchecked")
83+
public static <T> JsonpDeserializer<T> findDeserializer(Type type) {
84+
if (type instanceof Class<?>) {
85+
Class<?> clazz = (Class<?>)type;
86+
JsonpDeserializable annotation = clazz.getAnnotation(JsonpDeserializable.class);
87+
if (annotation != null) {
88+
try {
89+
Field field = clazz.getDeclaredField(annotation.field());
90+
return (JsonpDeserializer<T>)field.get(null);
91+
} catch (Exception e) {
92+
throw new RuntimeException("No deserializer found in '" + clazz.getName() + "." + annotation.field() + "'");
93+
}
8394
}
8495
}
8596

86-
if (clazz == Void.class) {
97+
if (type == Void.class) {
8798
return (JsonpDeserializer<T>)JsonpDeserializerBase.VOID;
8899
}
89100

java-client/src/main/java/co/elastic/clients/json/SimpleJsonpMapper.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import jakarta.json.spi.JsonProvider;
2424
import jakarta.json.stream.JsonGenerator;
2525

26+
import java.lang.reflect.Type;
2627
import java.util.HashMap;
2728
import java.util.Map;
2829

@@ -37,8 +38,8 @@ public class SimpleJsonpMapper extends JsonpMapperBase {
3738
public static SimpleJsonpMapper INSTANCE = new SimpleJsonpMapper(true);
3839
public static SimpleJsonpMapper INSTANCE_REJECT_UNKNOWN_FIELDS = new SimpleJsonpMapper(false);
3940

40-
private static final Map<Class<?>, JsonpSerializer<?>> serializers = new HashMap<>();
41-
private static final Map<Class<?>, JsonpDeserializer<?>> deserializers = new HashMap<>();
41+
private static final Map<Type, JsonpSerializer<?>> serializers = new HashMap<>();
42+
private static final Map<Type, JsonpDeserializer<?>> deserializers = new HashMap<>();
4243

4344
static {
4445
serializers.put(String.class, (JsonpSerializer<String>) (value, generator, mapper) -> generator.write(value));
@@ -117,14 +118,14 @@ public <T> void serialize(T value, JsonGenerator generator) {
117118
}
118119

119120
@Override
120-
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
121+
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
121122
@SuppressWarnings("unchecked")
122-
JsonpDeserializer<T> deserializer = (JsonpDeserializer<T>) deserializers.get(clazz);
123+
JsonpDeserializer<T> deserializer = (JsonpDeserializer<T>) deserializers.get(type);
123124
if (deserializer != null) {
124125
return deserializer;
125126
} else {
126127
throw new JsonException(
127-
"Cannot find a deserializer for type " + clazz.getName() +
128+
"Cannot find a deserializer for type " + type.getTypeName() +
128129
". Consider using a full-featured JsonpMapper"
129130
);
130131
}

java-client/src/main/java/co/elastic/clients/json/jackson/JacksonJsonpMapper.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import jakarta.json.stream.JsonParser;
3333

3434
import java.io.IOException;
35+
import java.lang.reflect.Type;
3536
import java.util.EnumSet;
3637

3738
public class JacksonJsonpMapper extends JsonpMapperBase {
@@ -76,8 +77,8 @@ public JsonProvider jsonProvider() {
7677
}
7778

7879
@Override
79-
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
80-
return new JacksonValueParser<>(clazz);
80+
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
81+
return new JacksonValueParser<>(type);
8182
}
8283

8384
@Override
@@ -103,11 +104,11 @@ public <T> void serialize(T value, JsonGenerator generator) {
103104

104105
private class JacksonValueParser<T> extends JsonpDeserializerBase<T> {
105106

106-
private final Class<T> clazz;
107+
private final Type type;
107108

108-
protected JacksonValueParser(Class<T> clazz) {
109+
protected JacksonValueParser(Type type) {
109110
super(EnumSet.allOf(JsonParser.Event.class));
110-
this.clazz = clazz;
111+
this.type = type;
111112
}
112113

113114
@Override
@@ -120,7 +121,7 @@ public T deserialize(JsonParser parser, JsonpMapper mapper, JsonParser.Event eve
120121
com.fasterxml.jackson.core.JsonParser jkParser = ((JacksonJsonpParser)parser).jacksonParser();
121122

122123
try {
123-
return objectMapper.readValue(jkParser, clazz);
124+
return objectMapper.readValue(jkParser, objectMapper().constructType(type));
124125
} catch(IOException ioe) {
125126
throw JacksonUtils.convertException(ioe);
126127
}

java-client/src/main/java/co/elastic/clients/json/jsonb/JsonbJsonpMapper.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import java.io.CharArrayReader;
3636
import java.io.CharArrayWriter;
37+
import java.lang.reflect.Type;
3738
import java.util.EnumSet;
3839

3940
public class JsonbJsonpMapper extends JsonpMapperBase {
@@ -60,8 +61,8 @@ public <T> JsonpMapper withAttribute(String name, T value) {
6061
}
6162

6263
@Override
63-
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
64-
return new Deserializer<>(clazz);
64+
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
65+
return new Deserializer<>(type);
6566
}
6667

6768
@Override
@@ -86,11 +87,11 @@ public JsonProvider jsonProvider() {
8687
}
8788

8889
private class Deserializer<T> extends JsonpDeserializerBase<T> {
89-
private final Class<T> clazz;
90+
private final Type type;
9091

91-
Deserializer(Class<T> clazz) {
92+
Deserializer(Type type) {
9293
super(EnumSet.allOf(JsonParser.Event.class));
93-
this.clazz = clazz;
94+
this.type = type;
9495
}
9596

9697
@Override
@@ -106,7 +107,7 @@ public T deserialize(JsonParser parser, JsonpMapper mapper, JsonParser.Event eve
106107
generator.close();
107108

108109
CharArrayReader car = new CharArrayReader(caw.toCharArray());
109-
return jsonb.fromJson(car, clazz);
110+
return jsonb.fromJson(car, type);
110111
}
111112
}
112113

java-client/src/main/java/co/elastic/clients/util/MissingRequiredPropertyException.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
* available in {@link ApiTypeHelper} to disable checks. Use with caution.
2727
*/
2828
public class MissingRequiredPropertyException extends RuntimeException {
29-
private Class<?> clazz;
30-
private String property;
29+
private final Class<?> clazz;
30+
private final String property;
31+
3132
public MissingRequiredPropertyException(Object obj, String property) {
3233
super("Missing required property '" + obj.getClass().getSimpleName() + "." + property + "'");
3334
this.clazz = obj.getClass();

0 commit comments

Comments
 (0)