Skip to content

[Backport 7.17] Add Type in addition to Class to map types with generic parameters #439

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

Merged
merged 1 commit into from
Nov 4, 2022
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
7 changes: 4 additions & 3 deletions java-client/src/main/java/co/elastic/clients/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import co.elastic.clients.json.JsonpMapperBase;

import javax.annotation.Nullable;
import java.lang.reflect.Type;
import java.util.function.Function;

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

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

return JsonpDeserializer.of(clazz);
return JsonpDeserializer.of(type);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import jakarta.json.stream.JsonParser;

import javax.annotation.Nullable;
import java.lang.reflect.Type;

public abstract class DelegatingJsonpMapper implements JsonpMapper {

Expand All @@ -39,8 +40,8 @@ public JsonProvider jsonProvider() {
}

@Override
public <T> T deserialize(JsonParser parser, Class<T> clazz) {
return mapper.deserialize(parser, clazz);
public <T> T deserialize(JsonParser parser, Type type) {
return mapper.deserialize(parser, type);
}

@Override
Expand Down
13 changes: 13 additions & 0 deletions java-client/src/main/java/co/elastic/clients/json/JsonData.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.util.EnumSet;

/**
Expand Down Expand Up @@ -59,11 +60,23 @@ public interface JsonData extends JsonpSerializable {
*/
<T> T to(Class<T> clazz);

/**
* Converts this object to a target type. A mapper must have been provided at creation time.
*
* @throws IllegalStateException if no mapper was provided at creation time.
*/
<T> T to(Type type);

/**
* Converts this object to a target class.
*/
<T> T to(Class<T> clazz, JsonpMapper mapper);

/**
* Converts this object to a target type.
*/
<T> T to(Type type, JsonpMapper mapper);

/**
* Converts this object using a deserializer. A mapper must have been provided at creation time.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Type;

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

@Override
public <T> T to(Class<T> clazz) {
return to((Type)clazz, null);
}

@Override
public <T> T to(Type clazz) {
return to(clazz, null);
}

@Override
public <T> T to(Class<T> clazz, JsonpMapper mapper) {
if (clazz.isAssignableFrom(value.getClass())) {
return (T) value;
return to((Type)clazz, mapper);
}

@Override
public <T> T to(Type type, JsonpMapper mapper) {
if (type instanceof Class<?> && ((Class<?>)type).isAssignableFrom(value.getClass())) {
@SuppressWarnings("unchecked")
T result = (T) value;
return result;
}

mapper = getMapper(mapper);

JsonParser parser = getParser(mapper);
return mapper.deserialize(parser, clazz);
return mapper.deserialize(parser, type);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import jakarta.json.stream.JsonParser.Event;

import java.io.StringReader;
import java.lang.reflect.Type;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -88,15 +89,23 @@ default V deserialize(JsonParser parser, JsonpMapper mapper) {

//---------------------------------------------------------------------------------------------

// /**
// * Creates a deserializer for a class that delegates to the mapper provided to
// * {@link #deserialize(JsonParser, JsonpMapper)}.
// */
// static <T>JsonpDeserializer<T> of(Class<T> clazz) {
// return of((Type)clazz);
// }

/**
* Creates a deserializer for a class that delegates to the mapper provided to
* Creates a deserializer for a type that delegates to the mapper provided to
* {@link #deserialize(JsonParser, JsonpMapper)}.
*/
static <T>JsonpDeserializer<T> of (Class<T> clazz) {
static <T>JsonpDeserializer<T> of(Type type) {
return new JsonpDeserializerBase<T>(EnumSet.allOf(JsonParser.Event.class)) {
@Override
public T deserialize(JsonParser parser, JsonpMapper mapper) {
return mapper.deserialize(parser, clazz);
return mapper.deserialize(parser, type);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import jakarta.json.stream.JsonParser;

import javax.annotation.Nullable;
import java.lang.reflect.Type;

/**
* A {@code JsonpMapper} combines a JSON-P provider and object serialization/deserialization based on JSON-P events.
Expand All @@ -44,7 +45,14 @@ public interface JsonpMapper {
/**
* Deserialize an object, given its class.
*/
<T> T deserialize(JsonParser parser, Class<T> clazz);
default <T> T deserialize(JsonParser parser, Class<T> clazz) {
return deserialize(parser, (Type)clazz);
}

/**
* Deserialize an object, given its type.
*/
<T> T deserialize(JsonParser parser, Type type);

/**
* Serialize an object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@

import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public abstract class JsonpMapperBase implements JsonpMapper {

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

private Map<String, Object> attributes;

Expand Down Expand Up @@ -61,29 +62,39 @@ protected JsonpMapperBase addAttribute(String name, Object value) {
}

@Override
public <T> T deserialize(JsonParser parser, Class<T> clazz) {
JsonpDeserializer<T> deserializer = findDeserializer(clazz);
public <T> T deserialize(JsonParser parser, Type type) {
JsonpDeserializer<T> deserializer = findDeserializer(type);
if (deserializer != null) {
return deserializer.deserialize(parser, this);
}

return getDefaultDeserializer(clazz).deserialize(parser, this);
@SuppressWarnings("unchecked")
T result = (T)getDefaultDeserializer(type).deserialize(parser, this);
return result;
}

@Nullable
@SuppressWarnings("unchecked")
public static <T> JsonpDeserializer<T> findDeserializer(Class<T> clazz) {
JsonpDeserializable annotation = clazz.getAnnotation(JsonpDeserializable.class);
if (annotation != null) {
try {
Field field = clazz.getDeclaredField(annotation.field());
return (JsonpDeserializer<T>)field.get(null);
} catch (Exception e) {
throw new RuntimeException("No deserializer found in '" + clazz.getName() + "." + annotation.field() + "'");
return findDeserializer((Type)clazz);
}

@Nullable
@SuppressWarnings("unchecked")
public static <T> JsonpDeserializer<T> findDeserializer(Type type) {
if (type instanceof Class<?>) {
Class<?> clazz = (Class<?>)type;
JsonpDeserializable annotation = clazz.getAnnotation(JsonpDeserializable.class);
if (annotation != null) {
try {
Field field = clazz.getDeclaredField(annotation.field());
return (JsonpDeserializer<T>)field.get(null);
} catch (Exception e) {
throw new RuntimeException("No deserializer found in '" + clazz.getName() + "." + annotation.field() + "'");
}
}
}

if (clazz == Void.class) {
if (type == Void.class) {
return (JsonpDeserializer<T>)JsonpDeserializerBase.VOID;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

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

private static final Map<Class<?>, JsonpSerializer<?>> serializers = new HashMap<>();
private static final Map<Class<?>, JsonpDeserializer<?>> deserializers = new HashMap<>();
private static final Map<Type, JsonpSerializer<?>> serializers = new HashMap<>();
private static final Map<Type, JsonpDeserializer<?>> deserializers = new HashMap<>();

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

@Override
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
@SuppressWarnings("unchecked")
JsonpDeserializer<T> deserializer = (JsonpDeserializer<T>) deserializers.get(clazz);
JsonpDeserializer<T> deserializer = (JsonpDeserializer<T>) deserializers.get(type);
if (deserializer != null) {
return deserializer;
} else {
throw new JsonException(
"Cannot find a deserializer for type " + clazz.getName() +
"Cannot find a deserializer for type " + type.getTypeName() +
". Consider using a full-featured JsonpMapper"
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import jakarta.json.stream.JsonParser;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.EnumSet;

public class JacksonJsonpMapper extends JsonpMapperBase {
Expand Down Expand Up @@ -76,8 +77,8 @@ public JsonProvider jsonProvider() {
}

@Override
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
return new JacksonValueParser<>(clazz);
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
return new JacksonValueParser<>(type);
}

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

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

private final Class<T> clazz;
private final Type type;

protected JacksonValueParser(Class<T> clazz) {
protected JacksonValueParser(Type type) {
super(EnumSet.allOf(JsonParser.Event.class));
this.clazz = clazz;
this.type = type;
}

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

try {
return objectMapper.readValue(jkParser, clazz);
return objectMapper.readValue(jkParser, objectMapper().constructType(type));
} catch(IOException ioe) {
throw JacksonUtils.convertException(ioe);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.lang.reflect.Type;
import java.util.EnumSet;

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

@Override
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
return new Deserializer<>(clazz);
protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
return new Deserializer<>(type);
}

@Override
Expand All @@ -86,11 +87,11 @@ public JsonProvider jsonProvider() {
}

private class Deserializer<T> extends JsonpDeserializerBase<T> {
private final Class<T> clazz;
private final Type type;

Deserializer(Class<T> clazz) {
Deserializer(Type type) {
super(EnumSet.allOf(JsonParser.Event.class));
this.clazz = clazz;
this.type = type;
}

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

CharArrayReader car = new CharArrayReader(caw.toCharArray());
return jsonb.fromJson(car, clazz);
return jsonb.fromJson(car, type);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
* available in {@link ApiTypeHelper} to disable checks. Use with caution.
*/
public class MissingRequiredPropertyException extends RuntimeException {
private Class<?> clazz;
private String property;
private final Class<?> clazz;
private final String property;

public MissingRequiredPropertyException(Object obj, String property) {
super("Missing required property '" + obj.getClass().getSimpleName() + "." + property + "'");
this.clazz = obj.getClass();
Expand Down
Loading