question

skodama avatar image
skodama asked skodama commented

Problem deserializing v3 REST responses containing arrays using gson

Hi,

I'm having problems deserializing v3 REST responses containing arrays using gson in a Java desktop application.

I have created classes that conforms to the Clover v3 REST specifications and I can successfully send requests using the jsonized objects of those classes to the REST service.

However, I can't deserialize the response from the REST service when it contains arrays, because every arrays in the response is encapsulated in a "elements" object, while the request json does not require the arrays to be inside a "elements" object.

Below is the code trying to deserialize the response returned from the request to get all the modifier groups with all the modifiers included:

    JsonParser parser = new JsonParser();
    JsonObject rootObject = parser.parse(response).getAsJsonObject();
    JsonElement listElement = rootObject.get("elements");

    Type listType = new TypeToken<List<ModifierGroup>>() {}.getType();
    List<ModifierGroup> modifierGroups= new Gson().fromJson(listElement, listType);

The error message that I get from gson is as follows:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT

I can workaround this problem by creating classes just for responses that have the arrays inside a "elements" object, or implement some kind of custom deserializer, but it would be much better if deserialization worked the same way as the serialization.

The following deserializer works for deserializing the responses correctly for the modifier group:

public static class ModifierGroupDeserializer implements JsonDeserializer<ModifierGroup> {

    @Override
    public ModifierGroup deserialize(JsonElement je, Type type,
            JsonDeserializationContext context) throws JsonParseException {
        JsonObject jo = je.getAsJsonObject();

        ModifierGroup mg = new ModifierGroup();
        mg.setAlternateName(getSafeString(jo.get("alternateName")));
        mg.setId(getSafeString(jo.get("id")));


        if(jo.get("items") != null)
        {
            JsonObject joItems = jo.get("items").getAsJsonObject();
            Type itemListType = new TypeToken<List<Item>>() {}.getType();
            mg.setItems((List<Item>)context.deserialize(joItems.get("elements"), itemListType));
        }
        mg.setMaxAllowed(getSafeString(jo.get("maxAllowed")));
        mg.setMinRequired(getSafeString(jo.get("maxRequired")));
        mg.setModifierIds(getSafeString(jo.get("modifierIds")));


        if(jo.get("modifiers") != null)
        {
            JsonObject joModifiers = jo.get("modifiers").getAsJsonObject();
            Type modifierListType = new TypeToken<List<Modifier>>() {}.getType();
            mg.setModifiers((List<Modifier>)context.deserialize(joModifiers.get("elements"), modifierListType));
        }
        mg.setName(getSafeString(jo.get("name")));
        mg.setShowByDefault(getSafeBoolean(jo.get("showByDefault"), true));

        return mg;
    }

    private boolean getSafeBoolean(JsonElement el, boolean def_val)
    {
        if(el != null)
        {
            return el.getAsBoolean();
        }

        return def_val;
    }

    private String getSafeString(JsonElement el)
    {
        if(el == null)
        {
            return "";
        }
        else
        {
            return el.getAsString();
        }
    }

}

Below is the pastebin link for the REST response for GET modifier_groups:

REST response JSON

Is there any recommendation on how to deserialize REST responses containing arrays?

4 comments
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

voski avatar image voski commented ·

Can you share the code that is throwing this.

0 Likes 0 ·
skodama avatar image skodama commented ·

Hi Voski, I have edited the post and added the code where the exception occurs.

0 Likes 0 ·
Jeffrey Blattman avatar image Jeffrey Blattman ♦♦ commented ·

can you include the JSON string you are having trouble with? perhaps include a pastebin link to it?

0 Likes 0 ·
skodama avatar image skodama commented ·

Hi jeff, I have added a pastebin link to the REST response JSON string.

0 Likes 0 ·
Jeffrey Blattman avatar image
Jeffrey Blattman answered skodama commented

If you want to go from our V3 JSON format to our SDK objects, simply do this,

TheSdkObject obj = new TheSdkObject(jsonString);

Where TheSdkObject could be a Payment, ModifierGroup, Order, etc. from the Clover Android SDK.

The objects aren't designed for GSON, and looking at the JSON you can see why it won't work. Our SDK objects are quite complex internally (you can look at the source if you want to check it out). They aren't simply bean-type objects.

If you absolutely need to use GSON, here are the GSON serializer, deserializer that will allow that.

public class CloverSdkSerializer<T extends JSONifiable> implements JsonSerializer<T> {
  @Override
  public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
    return new JsonPrimitive(src.getJSONObject().toString());
  }
}

and,

public class CloverSdkDeserializer<T extends JSONifiable> implements JsonDeserializer<T> {
  private final Class<T> cls;

  public CloverSdkDeserializer(Class<T> cls) {
    this.cls = cls;
  }

  @Override
  public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    String s = json.getAsJsonPrimitive().getAsString();
    try {
      Constructor<T> ctor = cls.getConstructor(String.class);
      return ctor.newInstance(s);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }
}

and use it like this,

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Payment.class, new CloverSdkSerializer<Payment>());
builder.registerTypeAdapter(Payment.class, new CloverSdkDeserializer<Payment>(Payment.class));
builder.registerTypeAdapter(Order.class, new CloverSdkSerializer<Order>());
builder.registerTypeAdapter(Order.class, new CloverSdkDeserializer<Order>(Order.class));
...

You can see that all they do internally is relies on the SDK object's inherent ability to go to / from JSON strings.

2 comments
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

skodama avatar image skodama commented ·

Thank you for the detailed codes and instructions, but we are not using the SDK objects, so unfortunately this would not work in our case.

0 Likes 0 ·
Jeffrey Blattman avatar image Jeffrey Blattman ♦♦ commented ·

Okay, then I recommend parsing using good old org.json.JSONObject. I suppose it's possible to author a proper serializer / deserializer though if that's the direction you want to go.

0 Likes 0 ·
Jacob Abrams avatar image
Jacob Abrams answered skodama commented

If you are running this code on a server or non-android device I believe the trick is to do the following: before using GSON or Jackson to map the JSON to an appropriate Java object first you must recursively walk through the JSON nodes and replace JSON object nodes with the name "elements" with a JSON List of the entries themselves. Then you can map the resulting JSON to a Java object.

2 comments
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

skodama avatar image skodama commented ·

That would be a much simpler solution to our case than implementing deserializers for all the affected objects. Thank you!

0 Likes 0 ·
skodama avatar image skodama commented ·

Instead of recursively walking through the JSON nodes, I simply removed the unecessary strings from the JSON string as follows, and it seems to work fine.

private static String unwrapJson(String json)
{
    json = json.replaceAll("\"elements\": ", "");
    json = json.replaceAll("\\Q{[\\E", "[");
    json = json.replaceAll("\\Q]}\\E", "]");

    if(json.startsWith("["))
    {
        json = "{\"elements\":" + json;
    }
    return json;
}
0 Likes 0 ·

Welcome to the
Clover Developer Community