2
\$\begingroup\$

i just had a problem converting the JSON from Phillips Hue Lights (Response) to a Model. Since it may contain several "unknown" keys in success and/or may contain just a String for success i had to write/use a TypeAdapter and a deserializer aswell. This works but im pretty sure there is a better way to solve this.

Dummy JSONS:

[{"success":{"/config/name":"My bridge"}}]

and

[{
    "success":{"id": "3"}
}]

and

[
{"success":{ "address": "/groups/1/action/on", "value": true}},
{"success":{ "address": "/groups/1/action/effect", "value":"colorloop"}},
{"success":{ "address": "/groups/1/action/hue", "value":6000}}
]

And the JSON with just a String

[{
    "success": "/config/whitelist/1234567890 deleted."
}]

It may also contain "error" but this isnt the problem. Since the object in address is unknown i use a Adapter from https://stackoverflow.com/questions/17627869/simple-way-to-strip-outer-array-of-responses-in-gson which works fine as long as its an Object. Since it may also contain a String i had to create a JsonDeserializer which validates if its a JSONObject and if not, it sets another variable in the Model with the message. It works, but i always have to revalidate if the message or success it set.

This is my JSONDeserializer

public class HueResponseDeserializer implements JsonDeserializer<HueResponse> {
    private final String TAG = HueResponseDeserializer.class.getSimpleName();

    @Override
    public HueResponse deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {

        HueResponse response = new HueResponse();
        JsonObject jsonObject = jsonElement.getAsJsonObject();
        if (jsonObject.get("success").isJsonObject()) {
            for (Map.Entry<String, JsonElement> entry : jsonObject.getAsJsonObject("success").entrySet()) {
                response.setSuccess(new HashMap<String, Object>() {{
                    put(entry.getKey(), entry.getValue());
                }});
            }
        } else {
            response.setSuccessMessage(jsonObject.get("success").getAsString());
        }

        if (jsonObject.has("error") && jsonObject.get("error").isJsonObject()) {
            for (Map.Entry<String, JsonElement> entry : jsonObject.getAsJsonObject("error").entrySet()) {
                response.setError(context.deserialize(entry.getValue(), HueResponse.Error.class));
            }
        }
        return response;
    }
}

And my Model

public class HueResponse {

    private Map<String, Object> success;
    private Error error;
    private String successMessage;

    public String getSuccessMessage() {
        return successMessage;
    }

    public void setSuccessMessage(String successMessage) {
        this.successMessage = successMessage;
    }

    public Map<String, Object> getSuccess() {
        return success;
    }

    public Error getError() {
        return error;
    }

    public void setSuccess(Map<String, Object> success) {
        this.success = success;
    }

    public void setError(Error error) {
        this.error = error;
    }

    public static class Error {
        private int type;
        private String address;
        private String description;

        public int getType() {
            return type;
        }

        public String getAddress() {
            return address;
        }

        public String getDescription() {
            return description;
        }


        @Override
        public String toString() {
            return "Type: " + this.type
                    + " Address: " + this.address
                    + " Description: " + this.description;
        }
    }

}
\$\endgroup\$
1
\$\begingroup\$

Iterating through an entrySet()

If what you really want is to put all the JSON objects into a success map, rather than overwriting each of them with every iteration, there's a way to stream it:

Map<String, Object> map = object.entrySet().stream()
                                .collect(Collectors.toMap(Entry::getKey, 
                                            entry -> (Object) entry.getValue()));
result.setSuccess(map);

In any case, a more fluent implementation of HashMap<>() {{ ... }} is to use Collections.singletonMap():

object.entrySet().forEach(entry -> { 
    Map<String, Object> map = Collections.singletonMap(entry.getKey(), entry.getValue()); 
    // ...
});

Modeling

You can consider have a more domain-based modeling of the successful results, instead of a simple Map<String, Object> in your HueResponse. For example, consider a Group of Action objects that have a state consisting of enabled (aka on), effect and hue? That seems to adhere closer to the results, giving you a more meaningful application.

\$\endgroup\$
  • 1
    \$\begingroup\$ Thank you, but does it really solve the issue? If i see that correctly its just to use Lambdas which generates one more function (if not two) -> youtube.com/watch?v=WALV33rWye4&t=1600s. The issue/problem is that the return value of success may or may not a JSONObject. It may also just a String or anything else. \$\endgroup\$ – Emanuel S Feb 14 '17 at 11:37
  • \$\begingroup\$ @EmanuelSeibold the first part addresses the double-brace initialization usage, which is unnecessary. The second is a suggestion on a richer model than just a mapping of JsonElement objects in your HueResponse class. \$\endgroup\$ – h.j.k. Feb 14 '17 at 13:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.