1

I need to parse a JSON file that contains long list of customers. In the JSON file each customer may have one id as a string:

{
  "cust_id": "87655",
  ...
},

or a few ids as an array:

   {
      "cust_id": [
        "12345",
        "45678"
      ],
      ...
    },

The Customer class is as below:

public class Customer {

    @SerializedName("cust_id")
    @Expose
    private String custId;
    public String getCustId() {
        return custId;
    }

    public void setCustId(String custId) {
        this.custId = custId;
    }
}

I parse the JSON using Gson:

Gson gson = new Gson()
Customers customers1 = gson.fromJson(json, Customers.class)

and it fails with com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY when it attempts to parse the array.

The reason of failure is clear.

My question: what is the best way to handle both cases (when id is a string and when it is an array of strings), given I can not change the json file structure?

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
Jacob
  • 1,135
  • 5
  • 14
  • 29
  • Possible duplicate of [Make GSON accept single objects where it expects arrays](http://stackoverflow.com/questions/43412261/make-gson-accept-single-objects-where-it-expects-arrays) – Lyubomyr Shaydariv May 02 '17 at 07:49
  • Please note that your question targets the subject that is pretty trivial at S.O. asking for "aligning" single and multiple cases into a list in the most efficient way, however the accepted answer does that in a pretty naive way: requires constructors, requires "cascaded exception unrolling", ignores particular `Gson` instances configuration, etc. – Lyubomyr Shaydariv May 02 '17 at 07:54

4 Answers4

2

If you want to handle both scenarios you can use a custom deserializer. Of course, you have to change the "cust_id" variable to be a list or an array.

Main:

String json1 = "{\"cust_id\": \"87655\"}";
String json2 = "{\"cust_id\": [\"12345\", \"45678\"]}";

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Customer.class, new CustomerDeserializer());
Gson gson = gsonBuilder.create();

Customer customer1 = gson.fromJson(json1, Customer.class);
System.out.println(customer1);

Customer customer2 = gson.fromJson(json2, Customer.class);
System.out.println(customer2);

Customer

public class Customer {

    @SerializedName("cust_id")
    private List<String> custId;

    public List<String> getCustId() {
        return custId;
    }

    public void setCustId(List<String> custId) {
        this.custId = custId;
    }
}

CustomerDeserializer

public class CustomerDeserializer implements JsonDeserializer<Customer> {

@Override
public Customer deserialize(JsonElement jsonElement, Type typeOf, JsonDeserializationContext context) throws JsonParseException {
    Customer result = null;
    Gson gson = new Gson();

    try {
        // try to deserialize by assuming JSON has a list
        result = gson.fromJson(jsonElement, Customer.class);
    } catch (JsonSyntaxException jse) {
        // error here means JSON has a single string instead of a list

        try {
            // get the single ID
            String custId = jsonElement.getAsJsonObject().get("cust_id").getAsString();

            result = new Customer();
            result.setCustId(Arrays.asList(new String[] {custId}));
        } catch (Exception e) {
            // more error handling here
            e.printStackTrace();
        }
    }

    return result;
}

}

Output

Customer [custId=[87655]]
Customer [custId=[12345, 45678]]
tima
  • 1,498
  • 4
  • 20
  • 28
0

You can just simply specify all values as array, even if is just one value.

{
  "cust_id": ["87655"],
  ...
},

UPDATE: If you cannot change the json, you can bind every field in Customer class except custId and set it manually.

public class Customer {
    private String[] custId;

    public String getCustId() {
        return custId;
    }

    public void setCustId(String custId) {
        custId = new String[] {custId};
    }

    public void setCustId(String[] custId) {
        this.custId = custId;
    }
}

And then parse manually:

Gson gson = new Gson();
Customers customers = gson.fromJson(json, Customers.class);
Object custId = new JSONObject(json).get("cust_id");
if (custId instanceof String) {
  customers.setCustId((String) custId);
else if (custId instanceof JSONArray) {
  customers.setCustId(convertToStringArray(new JSONArray(custId)));
}
alayor
  • 4,537
  • 6
  • 27
  • 47
0

Try method overriding:

public class Customer {

    @SerializedName("cust_id")
    @Expose
    private String custId;

    public void setCustId(String custId) {
        this.custId = {custId};
    }

    public String[] getCustId() {
        return custId;
    }

    @override
    public void setCustId(String[] custId) {
        this.custId = custId;
    }
}

Now In the code all values of CUSTID will be arrays instead of strings

Nishanth Matha
  • 5,993
  • 2
  • 19
  • 28
0

Refer this.

Now the problem is that you will have to write your own code on the returned map to get the desired result.

Ashish Lohia
  • 269
  • 1
  • 12