Moshi Unable to create converter for class with generic - java

Some of my server response for success are:
{
"error": null,
"data": null
}
My model is:
public class BaseResponse<E > {
#Json(name = "error")
public ErrorModel error;
#Json(name = "data")
public E data;
}
If my endpoint is
#POST("user")
Call<BaseResponse<String>> createUser(#Body Credentials credentials);
This works, but the type String is useless as data will always be null. But if i make it:
#POST("user")
Call<BaseResponse> createUser(#Body Credentials credentials);
I got crash at
Call<BaseResponse> call = apiService.createUser(creds);
full log:
java.lang.IllegalArgumentException: Unable to create converter for
class common.model.responses.BaseResponse for method MyEndpoints.createUser
...
Caused by: java.lang.IllegalArgumentException: Expected a Class, ParameterizedType,
or GenericArrayType, but <null> is of type null
at com.squareup.moshi.Types.getRawType(Types.java:167)
at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:83)
at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:91)
at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:91)
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:330)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:313)
at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:736)
...

This specific error surfaces because there is no way to know the type of the E field, since it was not specified in the raw type usage.
Since you know that this field will always be null, the best solution is to use a different type (that lacks the field) to clarify this.
If you really cannot use a different type, you could use Void as the generic type argument. You would need to register a JsonAdapter on your Moshi instance for the Void field to be deserialized, though: new Moshi.Builder().add(VOID_JSON_ADAPTER).build()
static final Object VOID_JSON_ADAPTER = new Object() {
#FromJson Void fromJson(JsonReader reader) throws IOException {
return reader.nextNull();
}
#ToJson void toJson(JsonWriter writer, Void v) throws IOException {
writer.nullValue();
}
};
But, using a different type makes the most sense.

Related

Class type of generic variable

I have data structure that looks more or less like this
class ResponseWrapper<T> {
T response;
public ResponseWrapper(T response) {
this.response = response;
}
}
And service that handles reading that response from JSON to actual DTO.
public class GenericService<T> {
public ResponseWrapper<T> read(String json, Class<T> clazz) throws Exception {
T response = new ObjectMapper().readValue(json, clazz);
return new ResponseWrapper<>(response);
}
}
And I can call it like this:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value", SomeData.class)
And what I'm trying to achieve is:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value")
And I'm wondering, is it actually possible? This is obviously not working
public ResponseWrapper<T> read(String json) throws Exception {
T response = new ObjectMapper().readValue(json, T.class);
return new ResponseWrapper<>(response);
}
No. It is not possible.
Java generics work by type erasure ... and that means that the actual class associated with generic type parameter is not available at runtime. If your code needs to know that class, you need to pass a Class object explicitly.
And, yes, T.class is a compilation error.
And, yes, there is no way to get the class of T.

An empty JSON field which is a boolean/nullable field in Java model, is getting converted as null

I have a specific requirement where in,
if a (boolean type) field in the request JSON body is absent altogether, then the request is valid.
If this field is set with a boolean value (true or false), then it is obviously valid.
If the field has non-boolean values, it should throw an exception.
If the field has empty value, it should throw an exception.
Even though the Java model throws an exception if the field is non-boolean by type checking, the empty value is being converted as null. Instead I need to be able to distinguish it and throw an exception. How can achieve this?
Here's how I defined the post body model and I am using the AutoValue generator so I don't have the setter function for the fields written by hand. Also, I am using the fasterxml library.
#JsonProperty("indicator")
#Nullable
public abstract Boolean getIndicator();
I have tried defining a custom annotation and writing the logic to check if the value is empty, but it didn't work.
You need to disable MapperFeature.ALLOW_COERCION_OF_SCALARS feature which allows coercion in cases like you have. See below example:
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonApp {
public static void main(String[] args) throws Exception {
String json = "{\"indicator\":\"\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
System.out.println(mapper.readValue(json, BaseClass.class));
}
}
class BaseClass {
private Boolean indicator;
public Boolean getIndicator() {
return indicator;
}
public void setIndicator(Boolean indicator) {
this.indicator = indicator;
}
#Override
public String toString() {
return "BaseClass{" +
"indicator=" + indicator +
'}';
}
}
Above code prints:
Exception in thread "main"
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
coerce empty String ("") to Null value for type java.lang.Boolean
(enable MapperFeature.ALLOW_COERCION_OF_SCALARS to allow) at
[Source: (String)"{"indicator":""}"; line: 1, column: 14] (through
reference chain: BaseClass["indicator"])

Java cast Object to MyClass

I'm writing a storage manager. I want to read from file to object. Example:
protected Object read(String key) throws Exception{
Path pathReadFrom = Paths.get(getPath(key));
if (!Files.isReadable(pathReadFrom)){
throw new FileNotFoundException();
}
Object object = JSON_MAPPER.readValue(Files.readAllBytes(pathReadFrom), Object.class);
return object;
}
JSON_MAPPER is Jackson's ObjectMapper.
public MyClass get(String id) throws Exception {
MyClass myClassObject = (MyClass) storeManager.read(id);
return myClassObject;
}
I get the next exception:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.pckg.project.dto.MyClass
How can I create universal read() method? Maybe should I set a type like second argument of read() method?
I've thought up some solution:
protected <T> T read(String key, Class<T> valueType) throws Exception{
Path pathReadFrom = Paths.get(getPath(key));
T object = JSON_MAPPER.readValue(Files.readAllBytes(pathReadFrom), valueType);
return object;
}
public MyClass get(String id) throws Exception {
MyClass object = storeManager.read(id, MyClass.class);
return object;
}
It works fine.
Yes, you do need to specify proper target type to use: otherwise Jackson has no idea of what kind of class you might want to get -- you are just telling it to "create a java.lang.Object out of JSON", which it happily does by using so-called "untyped" mode: JSON Objects become java.util.Maps, JSON Arrays java.util.Lists, JSON Strings regular Java Strings and so on.
So if you want to get JSON bound to Class MyClass, you will pass MyClass.class instead of Object.class.
How can you cast unrelated objects? In what way are LinkedHashMap and MyClass are related? You need to map them manually (or may be with help of something equivalent of AutoMapper for .NET)

Is Reflection needed to apply the correct generic adapter to my object dynamically

I am currently working on a serialization routine which uses a library of generically typed adapters. If the object being serialized is an instance of one of the specific adapters I have, then I need to call that adapter on the object prior to performing my other serialization procedures.
The following code works:
private final static String serialize(Object obj, Map<Class<?>,
XmlAdapter<?,?>> classToAdapterMap) throws JAXBException
{
Object adaptedObj = null;
for (Class<?> clazz : classToAdapterMap.keySet()) {
if (clazz.isInstance(obj)) {
XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
Class<?>[] argTypes = new Class[] {clazz};
try {
Method method = adapter.getClass().getMethod("marshal", argTypes);
adaptedObj = method.invoke(adapter, obj);
break;
} catch (Exception e) {
// handle method retrieval and invocation related exceptions
}
}
}
// serialize
}
However, I had originally thought that I would be able to do this more simply, for example with code like:
/* DOES NOT WORK */
private final static String serialize(Object obj, Map<Class<?>,
XmlAdapter<?,?>> classToAdapterMap) throws JAXBException
{
Object adaptedObj = null;
for (Class<?> clazz : classToAdapterMap.keySet()) {
if (clazz.isInstance(obj)) {
XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(clazz.cast(obj));
break;
}
}
// serialize
}
Clearly the problem is that the wildcard generically typed adapter isn't guaranteed to handle an object of type clazz. However, I can't indicate that these two are the same by changing the method signature—as I might otherwise do—to private final static <T> String serialize(Object obj, Map<Class<T>, XmlAdapter<?,T>> classToAdapterMap), because the map needs to hold adapters of all different types.
What would be a better way to do this? Or should I stick with the Reflection based solution?
Thanks in advance,
-Dan
There are several solutions to circumvent this problem.
Most likely, the easiest one is using raw types: don't specify the type parameters for the adapter, and the compiler will happily accept the marshall call (with a raw type warning of course):
XmlAdapter adapter = classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
(This is actually roughly the same solution as Bastian's, without the intermediate type)
If you don't like raw types, you may choose the unchecked cast to an Object-parameterized adapter. It's not really better, but it also works (by tricking the compiler…):
XmlAdapter<?, Object> adapter = (XmlAdapter<?, Object>) classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
My last solution is to use a type parameter at the method level. This time, what you do is semantically correct (as long as the map itself is correct), and the unchecked cast really means “I know what I am doing here”:
private final static <T> String serialize(T obj, Map<Class<?>,
XmlAdapter<?,?>> classToAdapterMap) throws JAXBException
{
Object adaptedObj = null;
for (Class<?> clazz : classToAdapterMap.keySet()) {
if (clazz.isInstance(obj)) {
try {
XmlAdapter<?, ? super T> adapter = (XmlAdapter<?, ? super T>) classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
break;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// serialize
}
The semantic correctness comes from the following:
you may consider T to be the actual class of obj since T is a method-bound parameter, not used elsewhere in the signature;
clazz is a super type of the type of T since we checked clazz.isInstance(obj);
adapter can handle instances of clazz or a super-type of it since it is how the map was built;
by consequent, adapter can handle all instances of an (unknown) super type of T, hence the ? super T declaration.
There is a simpler and safer way without using reflection:
At first, we need a small specialization of the XmlAdapter as it allows us to ask the adapter for the type it can handle.
public abstract class TalkingXmlAdapter<ValueType, BoundType> extends XmlAdapter<ValueType, BoundType> {
public abstract Class<BoundType> getBoundType();
}
My custom adapters now need to extend TalkingXmlAdapter:
public class AppleXmlAdapter extends TalkingXmlAdapter<String, Apple> {
#Override
public Class<Apple> getBoundType() {
return Apple.class;
}
#Override
public Apple unmarshal(String v) throws Exception {
System.out.println("Unmarshalling Apple");
return new Apple();
}
#Override
public String marshal(Apple v) throws Exception {
System.out.println("Marshalling Apple");
return "Apple";
}
}
public class BananaXmlAdapter extends TalkingXmlAdapter<String, Banana> {
#Override
public Class<Banana> getBoundType() {
return Banana.class;
}
#Override
public Banana unmarshal(String v) throws Exception {
System.out.println("Unmarshalling Banana");
return new Banana();
}
#Override
public String marshal(Banana v) throws Exception {
System.out.println("Marshalling Banana");
return "Banana";
}
}
That allows us to write a simplified serialization method:
public class SimpleSerializer {
public static final String serialize(Object obj, List<TalkingXmlAdapter> allAdapters) throws Exception {
Object adaptedObj = null;
for (TalkingXmlAdapter adapter : allAdapters) {
if (adapter.getBoundType().isInstance(obj)) {
adaptedObj = adapter.marshal(obj);
break;
}
}
// serialize
System.out.println("Simple serializing for " + obj.toString());
return "Simply serialized " + obj.toString();
}
}
Using the code e.g. like in the subsequent listing shows the behavior you want:
List<TalkingXmlAdapter> allAdapters = new ArrayList<>();
allAdapters.add(new AppleXmlAdapter());
allAdapters.add(new BananaXmlAdapter());
SimpleSerializer.serialize(new Banana(), allAdapters);
SimpleSerializer.serialize("Lemmon", allAdapters);
SimpleSerializer.serialize(new Apple(), allAdapters);
Output:
Marshalling Banana
Simple serializing for generic.adapter.Banana#659e0bfd
Simple serializing for Lemmon
Marshalling Apple
Simple serializing for generic.adapter.Apple#2a139a55
To sum this up, the solution gives you following advantages:
You don't need reflection which simplifies your code.
You need fewer generic programming in your serialization routine which simplifies your code.
The solution is more safe. Note that no type cast is needed. Every adapter accepts the type Object. However by using the generic method getBoundType() you can ensure the specific runtime type is the correct one. When building your map as in the reflection solution, a wrongly mapped class results in a runtime exception. In the proposed solution the super class TalkingXmlAdapter enforces each adapter to state their correct type by using generics.
The price you pay is:
Introduction of a new super type.
Requires small adaptions for your custom adapters.
Hope that helps!

How to invoke generic methods with runtime type information?

My program stores types of parameters mapped to the operation that accepts this type of parameter.
When using an explicit type to retrieve a stored operation, invoking the operation's method with an object of the given type as parameter is no problem.
However, when using a type that is only implicitly known, invoking the operation's method results in an error:
public class StoredArgumentTypeProblem {
static class Operation<T> {
T apply(T arg) {
return arg;
}
}
static class OperationContainer {
private Map<Class<?>, Operation<?>> storedOperations = new HashMap<>();
public <T> void put(Class<T> argType, Operation<T> opp) {
storedOperations.put(argType, opp);
}
public Class<?> getSomeStoredKey() {
return storedOperations.keySet().iterator().next();
}
public <T> Operation<T> get(Class<T> type) {
// unchecked cast, but should work given restrictions on put.
return (Operation<T>)storedOperations.get(type);
}
}
public void test() {
OperationContainer container = new OperationContainer();
container.put(Integer.class, new Operation<Integer>());
container.get(Integer.class).apply(new Integer(1234));
Class<?> keyType = container.getSomeStoredKey();
// ERROR: method apply in Operation<T> cannot be applied to given types
container.get(keyType).apply(keyType.cast(new Integer(5678)));
}
}
Of course, from Java's point of view the error is completely justified; capture #1 of '?' has nothing to do with capture #2 of '?'. But we humans can see that in this case invoking 'apply(…)' with an argument cast by keyType would work.
Is it possible to 'fool' Java and somehow dynamically apply the stored operation?
Using some type of casting? Using an Annotation? Any other ideas? …
This issue is related to limitations of wildcard capture. Wildcards essentially work like independent type parameters, and there's no way to express a relationship between them. As a workaround, you can use a "capture helper" method, which uses an actual type parameter to express that relationship:
private <T> void apply(
OperationContainer container,
Class<T> keyType,
Object argument
) {
T castArgument = keyType.cast(argument);
Operation<T> operation = container.get(keyType);
operation.apply(castArgument);
}
public void test() {
OperationContainer container = new OperationContainer();
container.put(Integer.class, new Operation<Integer>());
container.get(Integer.class).apply(new Integer(1234));
Class<?> keyType = container.getSomeStoredKey();
apply(container, keyType, new Integer(5678));
}
When writing the question above, the following solution occured to me.
To solve problems caused by generics and reflection …
use more reflection!
Given Operation and OperationContainer defined as above use Class.getMethod(…) and Method.invoke(…):
public void test() {
OperationContainer container = new OperationContainer();
container.put(Integer.class, new Operation<Integer>());
container.get(Integer.class).apply(new Integer(1234));
Class<?> keyType = container.getSomeStoredKey();
// ERROR: method apply in Operation<T> cannot be applied to given types
// container.get(keyType).apply(keyType.cast(new Integer(5678)));
Operation<?> storedOpp = container.get(keyType);
try {
storedOpp.getClass().getMethod("apply", keyType).invoke(storedOpp, keyType.cast(new Integer(5678)));
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException ex) {
throw new Error(ex);
}
}

Categories

Resources