Skip to content

Instantly share code, notes, and snippets.

@kamikat
Last active January 4, 2020 10:18
Show Gist options
  • Save kamikat/baa7d086f932b0dc4fc3f9f02e37a485 to your computer and use it in GitHub Desktop.
Save kamikat/baa7d086f932b0dc4fc3f9f02e37a485 to your computer and use it in GitHub Desktop.
Creates a JSON API Retrofit converter. https://github.com/kamikat/moshi-jsonapi#retrofit
package moe.banana.jsonapi2;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import moe.banana.jsonapi2.ArrayDocument;
import moe.banana.jsonapi2.Document;
import moe.banana.jsonapi2.ObjectDocument;
import moe.banana.jsonapi2.Resource;
import moe.banana.jsonapi2.ResourceIdentifier;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okio.Buffer;
import retrofit2.Converter;
import retrofit2.Retrofit;
@SuppressWarnings("unchecked")
public final class JsonApiConverterFactory extends Converter.Factory {
public static JsonApiConverterFactory create() {
return create(new Moshi.Builder().build());
}
public static JsonApiConverterFactory create(Moshi moshi) {
return new JsonApiConverterFactory(moshi, false);
}
private final Moshi moshi;
private final boolean lenient;
private JsonApiConverterFactory(Moshi moshi, boolean lenient) {
if (moshi == null) throw new NullPointerException("moshi == null");
this.moshi = moshi;
this.lenient = lenient;
}
public JsonApiConverterFactory asLenient() {
return new JsonApiConverterFactory(moshi, true);
}
private JsonAdapter<?> getAdapterFromType(Type type) {
Class<?> rawType = Types.getRawType(type);
JsonAdapter<?> adapter;
if (rawType.isArray() && ResourceIdentifier.class.isAssignableFrom(rawType.getComponentType())) {
adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType.getComponentType()));
} else if (List.class.isAssignableFrom(rawType) && type instanceof ParameterizedType) {
Type typeParameter = ((ParameterizedType) type).getActualTypeArguments()[0];
if (typeParameter instanceof Class<?> && ResourceIdentifier.class.isAssignableFrom((Class<?>) typeParameter)) {
adapter = moshi.adapter(Types.newParameterizedType(Document.class, typeParameter));
} else {
return null;
}
} else if (ResourceIdentifier.class.isAssignableFrom(rawType)) {
adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType));
} else if (Document.class.isAssignableFrom(rawType)) {
adapter = moshi.adapter(Types.newParameterizedType(Document.class, Resource.class));
} else {
return null;
}
return adapter;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
JsonAdapter<?> adapter = getAdapterFromType(type);
if (adapter == null) {
return null;
}
if (lenient) {
adapter = adapter.lenient();
}
return new MoshiResponseBodyConverter<>((JsonAdapter<Document<?>>) adapter, type);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
JsonAdapter<?> adapter = getAdapterFromType(type);
if (adapter == null) {
return null;
}
if (lenient) {
adapter = adapter.lenient();
}
return new MoshiRequestBodyConverter<>((JsonAdapter<Document<?>>) adapter, type);
}
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static class MoshiResponseBodyConverter<R> implements Converter<ResponseBody, R> {
private final JsonAdapter<Document<?>> adapter;
private final Class<R> rawType;
MoshiResponseBodyConverter(JsonAdapter<Document<?>> adapter, Type type) {
this.adapter = adapter;
this.rawType = (Class<R>) Types.getRawType(type);
}
@Override
public R convert(ResponseBody value) throws IOException {
try {
Document<?> document = adapter.fromJson(value.source());
if (Document.class.isAssignableFrom(rawType)) {
return (R) document;
} else if (List.class.isAssignableFrom(rawType)) {
ArrayDocument arrayDocument = document.asArrayDocument();
List a;
if (rawType.isAssignableFrom(ArrayList.class)) {
a = new ArrayList();
} else {
a = (List) rawType.newInstance();
}
a.addAll(arrayDocument);
return (R) a;
} else if (rawType.isArray()) {
ArrayDocument<?> arrayDocument = document.asArrayDocument();
Object a = Array.newInstance(rawType.getComponentType(), arrayDocument.size());
for (int i = 0; i != Array.getLength(a); i++) {
Array.set(a, i, arrayDocument.get(i));
}
return (R) a;
} else {
return (R) document.asObjectDocument().get();
}
} catch (InstantiationException e) {
throw new RuntimeException("Cannot find default constructor of [" + rawType.getCanonicalName() + "].", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access default constructor of [" + rawType.getCanonicalName() + "].", e);
} finally {
value.close();
}
}
}
private static class MoshiRequestBodyConverter<T> implements Converter<T, RequestBody> {
private final JsonAdapter<Document<?>> adapter;
private final Class<T> rawType;
MoshiRequestBodyConverter(JsonAdapter<Document<?>> adapter, Type type) {
this.adapter = adapter;
this.rawType = (Class<T>) Types.getRawType(type);
}
@Override
public RequestBody convert(T value) throws IOException {
Document document;
if (Document.class.isAssignableFrom(rawType)) {
document = (Document) value;
} else if (List.class.isAssignableFrom(rawType)) {
ArrayDocument arrayDocument = new ArrayDocument();
List a = ((List) value);
if (!a.isEmpty() && a.get(0) != null && ((ResourceIdentifier) a.get(0)).getContext() != null) {
arrayDocument = ((ResourceIdentifier) a.get(0)).getContext().asArrayDocument();
}
arrayDocument.addAll(a);
document = arrayDocument;
} else if (rawType.isArray()) {
ArrayDocument arrayDocument = new ArrayDocument();
if (Array.getLength(value) > 0 && ((ResourceIdentifier) Array.get(value, 0)).getContext() != null) {
arrayDocument = ((ResourceIdentifier) Array.get(value, 0)).getContext().asArrayDocument();
}
for (int i = 0; i != Array.getLength(value); i++) {
arrayDocument.add((ResourceIdentifier) Array.get(value, i));
}
document = arrayDocument;
} else {
ResourceIdentifier data = ((ResourceIdentifier) value);
ObjectDocument objectDocument = new ObjectDocument();
if (data.getContext() != null) {
objectDocument = data.getContext().asObjectDocument();
}
objectDocument.set(data);
document = objectDocument;
}
Buffer buffer = new Buffer();
adapter.toJson(buffer, document);
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
}
@luksha
Copy link

luksha commented Jul 24, 2017

+1 to add it to the main package 👍
I've just lost one day, because of this 🤕

@pboos
Copy link

pboos commented Sep 14, 2017

Should line 97 be

    private static final MediaType MEDIA_TYPE = MediaType.parse("application/vnd.api+json; charset=UTF-8");

Instead of

    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");

This to go along with the specification here http://jsonapi.org/format/#crud-creating

@SergeyVolynkin
Copy link

+1 to add it to the main package
and +1 to change according to @pboos

@douglasmarques
Copy link

@kaplanf Did you solve the java.lang.IllegalArgumentException? I am having the same problem

@kamikat
Copy link
Author

kamikat commented Apr 28, 2018

DEPRECATION

JsonApiConverterFactory is added to the repository by version 3.5.0 (documentation).

And this snippet is deprecated for the moshi-jsonapi-retrofit-converter library 🙏.

@aymenhs
Copy link

aymenhs commented May 4, 2018

thank you kamikat!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment