Hot questions for Using RxJava 2 in json

Question:

[UPDATED] Hi I'd need to cast the content of a CSV file into a JSON mapped by a Java class, using RxJava2. So for example, the CSV looks like this:

1,John,Smith

And the User Java class includes the properties

Long id;
String name;
String surname;

I'm not expert of RxJava2 however I'm trying with the following:

vertx.fileSystem().rxReadFile("/home/user/file.csv")

        .map(buffer -> buffer.toString("UTF-8"))
        .map(n -> n.replace(","," "))
        .map(JsonObject::new)
        .map(json -> json.mapTo(User.class))
        .subscribe(
                content -> System.out.println("Content: " + content),
                err -> System.out.println("Cannot read the file: " + err.getMessage())
        );

However it seems I'm on the wrong track:

Failed to decode: Cannot deserialize instance of `java.util.LinkedHashMap` out of VALUE_NUMBER_INT token

Any idea how to achieve it just using RxJava2 transformations? Thanks


Answer:

You are looking at this completely wrong. There is no automatic/magical conversion between CSV and JSON. If you want to generate User object from the CSV you provided you can use following code:

vertx.fileSystem().rxReadFile("/home/lbulic/file.csv")
            .map(buffer -> buffer.toString("UTF-8"))
            .map(n -> n.split(","))
            .map(data -> new User(Long.parseLong(data[0]), data[1], data[2]))
            .subscribe(content -> System.out.println("Content: " + content),
                    err -> System.out.println("Cannot read the file: " + err.getMessage()));

You will need constructor for User object and also toString method in order for this for correctly. If you want a JSON instead of the User object than the easy way is just to convert generated User object to JSON using Jackson.

rtx.fileSystem().rxReadFile("/home/lbulic/file.csv")
            .map(buffer -> buffer.toString("UTF-8"))
            .map(n -> n.split(","))
            .map(data -> new User(Long.parseLong(data[0]), data[1], data[2]))
            .map(user -> Json.encode(user))
            .subscribe(content -> System.out.println("Content: " + content),
                    err -> System.out.println("Cannot read the file: " + err.getMessage()));

For this to work you will also need getters and setters for the User object.

If you want direct conversion from CSV to JSON you will need to change the current structure of your CSV to something like this and will need to include additiona dependency.

CSV:

id,name,surname
1,John,Smith

Dependency:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-csv</artifactId>
    <version>2.9.9</version>
</dependency>

Code:

CsvSchema csvSchema = CsvSchema.builder().setUseHeader(true).build();
    CsvMapper csvMapper = new CsvMapper();

    vertx.fileSystem().rxReadFile("/home/lbulic/file.csv")
            .map(buffer -> buffer.toString("UTF-8"))
            .map(input -> csvMapper.readerFor(Map.class).with(csvSchema).readValues(input).readAll())
            .map(list -> list.stream().map(Json::encode).collect(Collectors.toList()))
            .subscribe(users -> {
                users.forEach(System.out::println);
            }, err -> System.out.println("Cannot read the file: " + err.getMessage()));

Question:

I want to read response from server, but I have problem with inner Json

Json object that cause me so many problem: :(

[
    {
        "num_id": 1,
        "row": {
            "coord_shirota": "59.932723",
            "www": "www.anichkov.ru",
            "metro": "Гостиный Двор",
            "description": "Старейшее здание на Невском проспекте, императорская резиденция",
            "address_manual": "Санкт-Петербург, Невский просп., 39 лит. А",
            "obj_history": "",
            "oid": "3285",
            "work_time": "пн-сб 10:00-18:00",
            "ogrn": "2147483647123",
            "metro_distance": "584.0",
            "phone": "8(812)310-93-80, 8(812)310-43-95.",
            "inn": "7840325991",
            "street": "Невский проспект",
            "metro_dolgota": "30.333745",
            "home": "39",
            "name_en": "Anichkov Palace",
            "metro_line": "Невско-Василеостровская линия",
            "name": "Аничков Дворец",
            "town": "Санкт-Петербург",
            "for_disabled": "0",
            "addressline": "Санкт-Петербург, Невский проспект, 39",
            "district": "Центральный район",
            "country": "Россия",
            "obj_history_en": "",
            "subdistrict": "муниципальный округ № 78",
            "coord_dolgota": "30.341459",
            "description_en": "The oldest building on Nevsky prospekt, imperial palace",
            "metro_shirota": "59.933938",
            "type": "Исторический",
            "email": "anichkovmuz@mail.ru"
        }
    },
    ...
]

My Api for request:

public interface SpbGovSiteApi {
    @Headers({ "Authorization: Token 60a368fde851f786532fdb4b65c6fdb189122666" })
    @GET("123/versions/latest/data?per_page=1000")
    Call<List<SpbGovSiteResponse>> getInfo();
}

My classes for response:

public class Row {
    private float coord_shirota;
    private float coord_dolgota;
    private String www;
    private String description;
    private String address_manual;
    private String name;
    //...getters
}

class SpbGovSiteResponse {
    private Row row;
    public Row getRow() {
        return row;
    }
}

My retrofit function:

public Observable<Map<String,String>> getInfo() {
    return spbGovSiteResponse(retrofit.create(SpbGovSiteApi.class).getInfo());
}

private Observable<Map<String,String>> spbGovSiteResponse(Call<List<SpbGovSiteResponse>> call){
    Log.e("gg",call.request().url().toString());
    return Observable.create(e -> {
        call.enqueue(new Callback<List<SpbGovSiteResponse>>() {

            @Override
            public void onResponse(Call<List<SpbGovSiteResponse>> call1, Response<List<SpbGovSiteResponse>> response) {
                for(SpbGovSiteResponse item: response.body()) {
                    Map map=new HashMap<String,String>();
                    //map.put("name",item.getRow().getName());
                    //map.put("longtide",item.getRow().getCoord_dolgota());
                    //map.put("latitude",item.getRow().getCoord_shirota());
                    e.onNext(map);
                }
                e.onComplete();
            }

            @Override
            public void onFailure(Call<List<SpbGovSiteResponse>> call1, Throwable t) {
                e.onError(t);
            }

        });
    });
}

And at last my problem:

In observer I print it in log with throwable parameter from interface method onFailed.

04-05 18:52:01.019 14703-14703/ru.scapegoats.museumspb W/System.err: java.lang.NumberFormatException: empty String
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at java.lang.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1071)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at java.lang.Double.parseDouble(Double.java:547)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.stream.JsonReader.nextDouble(JsonReader.java:909)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.Gson$3.read(Gson.java:308)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.Gson$3.read(Gson.java:302)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:119)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:218)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:112)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at okhttp3.RealCall$AsyncCall.execute(RealCall.java:141)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
04-05 18:52:01.020 14703-14703/ru.scapegoats.museumspb W/System.err:     at java.lang.Thread.run(Thread.java:761)

And if I left class SpbGovSiteApi empty, my programm working wery well... so I decided that cause of this problem there -_-

If i comment it

class SpbGovSiteResponse {
    private int num_id;
    // private Row row;
    // public Row getRow() {
    //     return row;
    // }
}

My program is running without problems


Answer:

You need to either make the type of the numbers String:

public class Row {
    private String coord_shirota; // <- String not float
    private String coord_dolgota; // <- String not float
    private String www;
    private String description;
    private String address_manual;
    private String name;
    //...getters
}

or make the response return them as numbers:

"row": {
            "coord_shirota": 59.932723,  <- NOT "59.932723"

Question:

Hi I am trying to make a changelog list from my REST API to my App, but I have some problems with this error line here:

Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

The JSON output:

{
    "forced": "true",
    "version": "1.0A(125)",
    "description": [
        {
            "description": "Fixed some things 1"
        },
        {
            "description": "Fixed some things 2"
        },
        {
            "description": "Fixed some things 3"
        },
        {
            "description": "Fixed some things 4"
        }
    ]
}

My REST Call:

@GET("updatecheck")
Observable<List<UpdateCheckResponse>> UpdateCheckChangeLog();

Here is my Pojo class named: UpdateCheckResponse.java

public class UpdateCheckResponse {
    @SerializedName("forced")
    @Expose
    private String forced;
    @SerializedName("version")
    @Expose
    private String version;
    @SerializedName("description")
    @Expose
    private List<UpdateCheckDescription> description = null;

    public String getForced() {
        return forced;
    }

    public void setForced(String forced) {
        this.forced = forced;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public List<UpdateCheckDescription> getDescription() {
        return description;
    }

    public void setDescription(List<UpdateCheckDescription> description) {
        this.description = description;
    }
}

My POJO named: UpdateCheckDescription.java

public class UpdateCheckDescription {
    @SerializedName("description")
    @Expose
    private String description;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

UpdateChangeLogAdapter.class

public class UpdateChangeLogAdapter extends RecyclerView.Adapter<UpdateChangeLogAdapter.ViewHolder> {

    private ArrayList<UpdateCheckDescription> mAndroidList;

    public UpdateChangeLogAdapter(ArrayList<UpdateCheckDescription> androidList) {
        mAndroidList = androidList;
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_update, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {

        holder.mTvChangelog.setText(mAndroidList.get(position).getDescription());
    }

    @Override
    public int getItemCount() {
        return mAndroidList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder{

        private TextView mTvChangelog;
        public ViewHolder(View view) {
            super(view);

            mTvChangelog = (TextView)view.findViewById(R.id.descriptionTextView);
        }
    }
}

And here is the call from my UpdateActivity class:

private void loadJSON() {

    FitnessM8REST apiService =
            FitnessM8RestClient.getClient().create(FitnessM8REST.class);

    mCompositeDisposable.add(apiService.UpdateCheckChangeLog()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
            .subscribe(this::handleResponse,this::handleError));
}

private void handleResponse(List<UpdateCheckResponse> androidList) {

    mAndroidArrayList = new ArrayList<>(androidList);
    mAdapter = new UpdateChangeLogAdapter(mAndroidArrayList);
    mRecyclerView.setAdapter(mAdapter);
}

private void handleError(Throwable error) {
    Toast.makeText(this, "Error "+error.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
    Log.d("Error: ", error.getLocalizedMessage());

}

Answer:

See you are doing a wrong call

your method should be

@GET("updatecheck")Call<UpdateCheckResponse> methodName();

and while handling the response you can do something like

List<UpdateCheckDescription> description;
description = response.body().getDescription();

and then pass that description Collection object in your adapter. You are calling an List object directly to response but actually the Array of objects is inside an object