1

I have a problem with parsing my custom response because the I have a response with Localization properties.

I am recieving a response that looks something like this:

[
    {
        "id": "dummyID1",
        "name.en_US": "dummyNameEn1",
        "name.fi_FI": "dummyNameFi1"
    },
    {
        "id": "dummyID2",
        "name.en_US": "dummyNameEn2",
        "name.fi_FI": "dummyNameFi2"
    },
    {
        "id": "dummyID3",
        "name.en_US": "dummyNameEn3",
        "name.fi_FI": "dummyNameFi3"
    }...
]

And to parse that I have created a custom class Device.java:

public class Device {

    public String id;
    public LocalizedString name;

    public Device(String id, LocalizedString name) {
        this.id = id;
        this.name = name;
    }
    //Getters and setters
}

Now here we have a custom object named LocalizedString.java:

public class LocalizedString implements Parcelable {

    public static final Creator<LocalizedString> CREATOR = new Creator<LocalizedString>() {
        @Override
        public LocalizedString createFromParcel(Parcel in) {
            return new LocalizedString(in);
        }

        @Override
        public LocalizedString[] newArray(int size) {
            return new LocalizedString[size];
        }
    };

    private String en_US;
    private String fi_FI;

    public LocalizedString(String en, String fi) {
        this.en_US = en;
        this.fi_FI = fi;
    }

    protected LocalizedString(Parcel in) {
        en_US = in.readString();
        fi_FI = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(en_US);
        dest.writeString(fi_FI);
    }
//Getters, setters
}

Now in my response I want to create a list of Device's but it does not seem to understand how the ´LocalizedString´ works. Since my request is returning a <List<Device>> I cannot really customly parse it either.

Here is how I try to parse it:

        Call<List<Device>> call = getMainActivity().getRestClient().getDevices();
        call.enqueue(new Callback<List<Device>>() {
            @Override
            public void onResponse(Call<List<Device>> call, Response<List<Device>> response) {
                if (isAttached()) {
                    if (response.isSuccessful()) {
                        // get data
                       List<Device> items = response.body();

                    }
                }
            }

            @Override
            public void onFailure(Call<List<Device>> call, Throwable t) {
                if (isAttached()) {
                    Logger.debug(getClass().getName(), "Could not fetch installation document devices past orders", t);
                    getMainActivity().showError(R.string.error_network);
                }
            }
        });

And:

@GET("document/devices")
Call<List<Device>> gettDevices();

What am I supposed to do in this situation to bind the name to the Device and later be able to either get en_US or fi_FI.

Richard
  • 1,087
  • 18
  • 52
  • You can replace `name.` to `empty` string before parsing, it will work fine then. – Sunny Nov 22 '19 at 07:40
  • @Sunny Sorry, I tried to understand it but I could not. What did you mean by replacing `name.` with `empty` – Richard Nov 22 '19 at 07:49
  • I mean when you get json string in that string replace `"name.en_US"` to `en_US` – Sunny Nov 22 '19 at 07:50
  • @Sunny So you mean manually go over the response and replace all of the `name.en_US` to `en_US` and then the same for the other language? – Richard Nov 22 '19 at 07:58
  • using `replaceAll("name.", "")` in response string and then parse – Sunny Nov 22 '19 at 08:11
  • @Sunny I understand now what you mean but that will not work in my opinion since when doing the `Call` method, it is trying to manually parse the response beforehand and all that I am left with in my response is the `Response> response` which has all the ID's like it should but all the names are `null`. What I asssume should be done here is to teach the `Device` class to interpret the response because at the moment it does not understand what that means. – Richard Nov 22 '19 at 08:33

3 Answers3

4

Better you can write it like this

public class Device {

    @SerializedName("id")
    public String id;

    @SerializedName("name.en_US")
    public String en;

    @SerializedName("name.fi_FI")
    public String fi;

    public Device(String id, String english, String fi) {
        this.id = id;
        this.en = english;
        this.fi = fi;
     }
    //Getters and setters
}
Ankit Aman
  • 999
  • 6
  • 15
  • It should be `String` instead of the `LocalizedString` in the constructor i assume? – Richard Nov 25 '19 at 07:58
  • And also adding `this.name = new LocalizedString(english, fi);` to the Constructor doesnt seem to work, but I do need the `LocalizedString`, what am I doing wrong here? – Richard Nov 25 '19 at 08:27
  • @kataroty Sorry my bad. – Ankit Aman Nov 25 '19 at 08:36
  • If my guess is correct then it doesnt even go to the `constructor` to map the value to the `LocalizedString`. Any idea how to assign value to that? – Richard Nov 25 '19 at 08:41
  • yes It generally use a no-argument (default) construtor to instantiate an object and set its fields via reflection. Can you describe what you are expecting? – Ankit Aman Nov 25 '19 at 08:42
  • Well I need to set the `LocalizedString` in the `Device` and since it is not being set in the adapter then my question is: Do you know how to set it without using the adapter? Basically I need to do `new LocalizedString(en, fi)` – Richard Nov 25 '19 at 09:02
  • write a getter inside Device like-> public LocalizedString getLocalizedStrings() { return new LocalizedString(en, fi); } – Ankit Aman Nov 25 '19 at 09:07
  • Not sure if that is the best practise but is seems to work. Thank you a lot for your help. – Richard Nov 25 '19 at 09:15
1

If you can control the source of the JSON, then a modification of that JSON structure is easy to solve your problem.

If you can not, the one way we can use to solve your problem is to use Jackson and custom deserializer:

public class DeviceDeserializer extends StdDeserializer<Device> { 

    public DeviceDeserializer() { 
        this(null); 
    } 

    public DeviceDeserializer(Class<?> vc) { 
        super(vc); 
    }

    @Override
    public Device deserialize(JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        String id = getStringValue(node, "id");
        String en = getStringValue(node, "name.en_EN");
        String fi = getStringValue(node, "name.fi_FI");
        LocalizedString localized = new LocalizedString(en, fi);
        return new Device(id, localizedString);
    }

    private String getStringValue(JsonNode node, String key) {
        // Throws exception or use null is up to you to decide
        return Optional.ofNullable(node.get("id"))
                       .map(JsonNode::asText)
                       .orElse(null);
    }
}

Manually register the deserializer yourself or using the annotation:

@JsonDeserialize(using = DeviceDeserializer.class)
public class Device {
...

Note that you must enable retrofit jackson converter plugin: (see the Retrofit Configuration part)

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(JacksonConverterFactory.create())
    .build();
Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
0

Read this: Get nested JSON object with GSON using retrofit

Sina
  • 2,683
  • 1
  • 13
  • 25