12

I'm working with an API that sometimes contains a list of child objects:

{ 'obj' : { children: [ {id: "1"}, {id: "2"} ] } }

I can parse this no problem. But if there just one child it doesn't return it as a list:

{ 'obj' : { children: {id: "1"} } }

My parser which expects a list then breaks. Does anyone have a suggestion for how to deal with this?

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
ufmemo
  • 211
  • 2
  • 6
  • 1
    I don't think this is a problem with Gson but with whatever is creating the data in the first place. In the JSON examples you show, in one case children is a list and in one case it's an object. – Daniel Pryden Jun 03 '11 at 07:02

1 Answers1

16

With Gson, the only way I know how to handle situations like this is with a custom Deserializer. For example:

// outputs:
// [Container: obj=[ChildContainer: children=[[Child: id=1], [Child: id=2]]]]
// [Container: obj=[ChildContainer: children=[[Child: id=1]]]]

public class Foo
{
  static String json1 = "{\"obj\":{\"children\":[{\"id\":\"1\"},{\"id\":\"2\"}]}}";
  static String json2 = "{\"obj\":{\"children\":{\"id\":\"1\"}}}";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    gsonBuilder.registerTypeAdapter(Child[].class, new ChildrenDeserializer());
    Gson gson = gsonBuilder.create();
    Container container1 = gson.fromJson(json1, Container.class);
    System.out.println(container1);

    Container container2 = gson.fromJson(json2, Container.class);
    System.out.println(container2);
  }
}

class Container
{
  ChildContainer obj;

  @Override
  public String toString()
  {
    return String.format("[Container: obj=%1$s]", obj);
  }
}

class ChildContainer
{
  Child[] children;

  @Override
  public String toString()
  {
    return String.format("[ChildContainer: children=%1$s]", Arrays.toString(children));
  }
}

class Child
{
  String id;

  @Override
  public String toString()
  {
    return String.format("[Child: id=%1$s]", id);
  }
}

class ChildrenDeserializer implements JsonDeserializer<Child[]>
{
  @Override
  public Child[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    if (json instanceof JsonArray)
    {
      return new Gson().fromJson(json, Child[].class);
    }
    Child child = context.deserialize(json, Child.class);
    return new Child[] { child };
  }
}
Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97
  • To point out a couple of obvious things: 1. In the custom deserializer, instead of using a new Gson object to handle the Child[], it would be simple to just iterate through the JsonArray, and individually deserialize each Child using the existing JsonDeserializationContext, adding the result as a component to a new Child[] which would then be returned by the custom deserializer. This would have the advantage of reusing the already-configured Gson deserializer. – Programmer Bruce Jun 20 '11 at 22:18
  • 2. Setting a FieldNamingPolicy on the GsonBuilder was unnecessary for this example. It was just something left over from copy-pasted code that I didn't delete. – Programmer Bruce Jun 20 '11 at 22:18
  • The solution with Jackson is simpler as it has a built-in feature to handle this particular situation. Just configure the ObjectMapper to ACCEPT_SINGLE_VALUE_AS_ARRAY, with a call such as mapper.getDeserializationConfig().set(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); and then no other custom deserialization is necessary. Both JSON input examples I posted above will deserialize with just one line of code. Container c = mapper.readValue(json, Container.class); (Provided the fields in the Java data structure are all made public, or otherwise exposed as described at http://goo.gl/YuYhx ) – Programmer Bruce Jun 26 '11 at 20:07