Last active
April 15, 2016 15:25
-
-
Save Gregadeaux/cbcc3781fda9d7fa6498 to your computer and use it in GitHub Desktop.
JSONAPI Converter for RetroFit with RxJava and RetroLambda
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ai.cometandroid.network; | |
import com.google.gson.Gson; | |
import com.google.gson.GsonBuilder; | |
import com.google.gson.internal.LinkedHashTreeMap; | |
import java.io.BufferedReader; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.InputStreamReader; | |
import java.io.OutputStream; | |
import java.io.UnsupportedEncodingException; | |
import java.lang.reflect.Type; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Map; | |
import ai.cometandroid.models.JsonApiResponse; | |
import retrofit.converter.ConversionException; | |
import retrofit.converter.Converter; | |
import retrofit.mime.TypedInput; | |
import retrofit.mime.TypedOutput; | |
import rx.Observable; | |
/** | |
* Created by greg on 6/26/15. | |
*/ | |
public class JsonApiConverter implements Converter { | |
private static String RELATIONSHIP_KEY = "relationships"; | |
private static String ATTRIBUTES_KEY = "attributes"; | |
@Override | |
public Object fromBody(TypedInput body, Type type) throws ConversionException { | |
try { | |
InputStream in = body.in(); | |
String json = fromJsonApi(fromStream(in)); | |
if(String.class.equals(type)) return json; | |
else return new Gson().fromJson(json, type); | |
} catch (Exception e) { | |
throw new ConversionException(e); | |
} | |
} | |
@Override | |
public TypedOutput toBody(Object object) { | |
try { | |
Map<String, Object> jsonApi = new LinkedHashTreeMap<>(); | |
jsonApi.put("data", object); | |
return new JsonTypedOutput(new Gson().toJson(jsonApi).getBytes("UTF-8")); | |
} catch (UnsupportedEncodingException e) { | |
throw new AssertionError(e); | |
} | |
} | |
private static class JsonTypedOutput implements TypedOutput { | |
private final byte[] jsonBytes; | |
JsonTypedOutput(byte[] jsonBytes) { this.jsonBytes = jsonBytes; } | |
@Override public String fileName() { return null; } | |
@Override public String mimeType() { return "application/json; charset=UTF-8"; } | |
@Override public long length() { return jsonBytes.length; } | |
@Override public void writeTo(OutputStream out) throws IOException { out.write(jsonBytes); } | |
} | |
private static String fromStream(InputStream in) throws IOException { | |
BufferedReader reader = new BufferedReader(new InputStreamReader(in)); | |
StringBuilder out = new StringBuilder(); | |
String line; | |
while ((line = reader.readLine()) != null) { | |
out.append(line); | |
out.append("\r\n"); | |
} | |
return out.toString(); | |
} | |
private static String fromJsonApi(String json) throws Exception { | |
Gson gson = new Gson(); | |
JsonApiResponse response = gson.fromJson(json, JsonApiResponse.class); | |
List<Map<String, Object>> data = new ArrayList<>(); | |
response.data() | |
.doOnNext(stringObjectMap -> { | |
stringObjectMap.putAll((Map<String, Object>) stringObjectMap.get(ATTRIBUTES_KEY)); | |
stringObjectMap.remove(ATTRIBUTES_KEY); | |
Observable.from(((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).keySet()) | |
.filter(key -> ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key) instanceof Map || ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key) instanceof List) | |
.subscribe(key -> { | |
stringObjectMap.put(key, ((Map<String, Object>) ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key)).get("data")); | |
Observable<Map<String, Object>> inner; | |
Object includedLinks = stringObjectMap.get(key); | |
if (includedLinks instanceof List) inner = Observable.from((List<Map<String, Object>>) includedLinks); | |
else inner = Observable.just((Map<String, Object>) includedLinks); | |
inner.forEach(link -> response.included() | |
.filter(included -> included.get("type").equals(link.get("type")) && included.get("id").equals(link.get("id"))) | |
.first() | |
.subscribe(included -> link.putAll((Map<String, Object>) included.get(ATTRIBUTES_KEY)))); | |
}); | |
stringObjectMap.remove(RELATIONSHIP_KEY); | |
}) | |
.subscribe(datum -> data.add(datum)); | |
String formatted; | |
if(data.size() == 1) { | |
formatted = gson.toJson(data.get(0)); | |
}else { | |
formatted = gson.toJson(data); | |
} | |
return formatted; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ai.cometandroid.models; | |
import java.util.List; | |
import java.util.Map; | |
import rx.Observable; | |
/** | |
* Created by greg on 6/26/15. | |
*/ | |
public class JsonApiResponse { | |
private Object data; | |
private List<Map<String, Object>> included; | |
private Map<String, Object> meta; | |
private Map<String, Object> links; | |
public Observable<Map<String, Object>> included() { | |
return Observable.from(included); | |
} | |
public Observable<Map<String, Object>> data() { | |
if(data instanceof List) return Observable.from((List<Map<String,Object>>)data); | |
else return Observable.just((Map<String,Object>)data); | |
} | |
} |
For those still stuck on Java 7:
import com.google.gson.Gson;
import com.google.gson.internal.LinkedHashTreeMap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import retrofit.converter.ConversionException;
import retrofit.converter.Converter;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;
public class JsonApiConverter implements Converter {
private String RELATIONSHIP_KEY = "relationships";
private String ATTRIBUTES_KEY = "attributes";
@Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
try {
InputStream in = body.in();
String json = fromJsonApi(fromStream(in));
if (String.class.equals(type)) {
return json;
} else {
return new Gson().fromJson(json, type);
}
} catch (Exception e) {
throw new ConversionException(e);
}
}
@Override
public TypedOutput toBody(Object object) {
try {
Map<String, Object> jsonApi = new LinkedHashTreeMap<>();
jsonApi.put("data", object);
return new JsonTypedOutput(new Gson().toJson(jsonApi).getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
private class JsonTypedOutput implements TypedOutput {
private final byte[] jsonBytes;
JsonTypedOutput(byte[] jsonBytes) {
this.jsonBytes = jsonBytes;
}
@Override
public String fileName() {
return null;
}
@Override
public String mimeType() {
return "application/json; charset=UTF-8";
}
@Override
public long length() {
return jsonBytes.length;
}
@Override
public void writeTo(OutputStream out) throws IOException {
out.write(jsonBytes);
}
}
private String fromStream(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
out.append(line);
out.append("\r\n");
}
return out.toString();
}
private String fromJsonApi(String json) throws Exception {
Gson gson = new Gson();
final JsonApiResponse response = gson.fromJson(json, JsonApiResponse.class);
final List<Map<String, Object>> data = new ArrayList<>();
response.data()
.doOnNext(new Action1<Map<String, Object>>() {
@Override
public void call(final Map<String, Object> stringObjectMap) {
stringObjectMap.putAll((Map<String, Object>) stringObjectMap.get(ATTRIBUTES_KEY));
stringObjectMap.remove(ATTRIBUTES_KEY);
Observable.from(((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).keySet())
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(final String key) {
return ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key) instanceof Map || ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key) instanceof List;
}
})
.subscribe(new Action1<String>() {
@Override
public void call(final String key) {
stringObjectMap.put(key, ((Map<String, Object>) ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key)).get("data"));
Observable<Map<String, Object>> inner;
Object includedLinks = stringObjectMap.get(key);
if (includedLinks instanceof List) {
inner = Observable.from((List<Map<String, Object>>) includedLinks);
} else {
inner = Observable.just((Map<String, Object>) includedLinks);
}
inner.forEach(new Action1<Map<String, Object>>() {
@Override
public void call(final Map<String, Object> link) {
response.included()
.filter(new Func1<Map<String, Object>, Boolean>() {
@Override
public Boolean call(final Map<String, Object> included) {
return included.get("type").equals(link.get("type")) && included.get("id").equals(link.get("id"));
}
})
.first()
.subscribe(new Action1<Map<String, Object>>() {
@Override
public void call(final Map<String, Object> included) {
link.putAll((Map<String, Object>) included.get(ATTRIBUTES_KEY));
}
});
}
});
}
});
stringObjectMap.remove(RELATIONSHIP_KEY);
}
})
.subscribe(new Action1<Map<String, Object>>() {
@Override
public void call(final Map<String, Object> datum) {
data.add(datum);
}
});
String formatted;
if (data.size() == 1) {
formatted = gson.toJson(data.get(0));
} else {
formatted = gson.toJson(data);
}
return formatted;
}
}
Good call. This was built using retrolamda.
@Gregadeaux how would you implement this for retrofit2 ?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello,
Could you provide an example which is using you converter?