Skip to content

Instantly share code, notes, and snippets.

@Gregadeaux
Last active April 15, 2016 15:25
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Gregadeaux/cbcc3781fda9d7fa6498 to your computer and use it in GitHub Desktop.
Save Gregadeaux/cbcc3781fda9d7fa6498 to your computer and use it in GitHub Desktop.
JSONAPI Converter for RetroFit with RxJava and RetroLambda
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;
}
}
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);
}
}
@kokeroulis
Copy link

Hello,

Could you provide an example which is using you converter?

@jaredsburrows
Copy link

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;
    }
}

@Gregadeaux
Copy link
Author

Good call. This was built using retrolamda.

@tagrudev
Copy link

@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