question

cdcilley avatar image
cdcilley asked keithryanwong Deactivated commented

REST API : bulk_line_items JSONException

I've started working with the REST API. So far I can get an access_token, merchant information, list orders, create a new order and add a single line item to that order. When I try to make a call to bulk_line_items Clover sends an error.

Here's my Java code for creating the JSON (I'm generating random values for the line items)...
JSONObject bulkItems = new JSONObject();
JSONArray ja = new JSONArray();
JSONObject jo;
for (int i = 0; i < 3; i++) {
   jo = new JSONObject();
   jo.put("name", IdUtils.getRandomString(6));
   jo.put("price", (long) random.nextInt(2000));
   jo.put("quantitySold", df.format(random.nextDouble() * 10));
   jo.put("unitName", "ea");
   ja.put(jo);
}
bulkItems.put("items", ja);
When I convert bulkItems to a JSON string, it looks exactly like the sample string in the REST api_docs.
{
	"items": [{
		"name": "WTGDC9",
		"price": 1725,
		"quantitySold": "2.44",
		"unitName": "ea"
	},
	{
		"name": "0JPYJS",
		"price": 663,
		"quantitySold": "6.07",
		"unitName": "ea"
	},
	{
		"name": "TP7QP9",
		"price": 1211,
		"quantitySold": "1.53",
		"unitName": "ea"
	}]
}
When I send this to the bulk_line_items endpoint, I get the error message:
org.json.JSONException: Value [{"id":"Y8ESY2SP7MZ9G","name":"WTGDC9","price":1725,"unitName":"ea","printed":false,"createdTime":1522909087000,"orderClientCreatedTime":1522909087000,"exchanged":false,"refunded":false,"isRevenue":true},{"id":"Q7TVNDJ48WJA2","name":"0JPYJS","price":663,"unitName":"ea","printed":false,"createdTime":1522909087000,"orderClientCreatedTime":1522909087000,"exchanged":false,"refunded":false,"isRevenue":true},{"id":"YZ0QXRAAP0F44","name":"TP7QP9","price":1211,"unitName":"ea","printed":false,"createdTime":1522909087000,"orderClientCreatedTime":1522909087000,"exchanged":false,"refunded":false,"isRevenue":true}] of type org.json.JSONArray cannot be converted to JSONObject

Any help on what I'm doing wrong?
OrdersREST APILineItems
1 comment
10 |2000

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

cdcilley avatar image cdcilley commented ·

I'm figuring out this may be a an issue with Volley (com.android.volley). The REST endpoint wants a JsonObject, but returns a JsonArray. The default JsonObjectRequest and JsonArrayRequest classes in Volley don't mix and match like that. I think I'm on to a fix and will post. Unless someone already knows...

0 Likes 0 ·

1 Answer

cdcilley avatar image
cdcilley answered keithryanwong Deactivated commented
If you are using com.android.volley to make calls to the Clover REST API, use of StringRequest works to get a response that can be parsed as JSON. JsonObjectRequest works for calls that require a JsonObject, for example Orders->LineItems (POST /v3/merchants/{mId}/orders/{orderId}/line_items).

However, Orders-BulkLineItems, takes a JsonObject for POSTing, but returns a JsonArray. (There may be other calls that have this setup, but I first bumped up against this one). The Volley JsonObjectRequest and JsonArrayRequest, both expect the POST and response to be the same Json type.

After some searching I found the solution is to have a Custom Request for Volley. I used this StackOverflow answer as a basis for my solution.

BulkItemRequest.java
package bjccproductionsltd.com.basicgo;

import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;
import com.android.volley.toolbox.JsonRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;

/**
 * Created by Xtopher on 4/5/2018.
 * From https://stackoverflow.com/a/30859412/588556
 */
public class BulkItemsRequest extends JsonRequest<JSONArray> {
	protected static final String PROTOCOL_CHARSET = "utf-8";
	/**
	 * Creates a new request.
	 *
	 * @param method        the HTTP method to use
	 * @param url           URL to fetch the JSON from
	 * @param requestBody   A {@link String} to post with the request. Null is allowed and
	 *                      indicates no parameters will be posted along with request.
	 * @param listener      Listener to receive the JSON response
	 * @param errorListener Error listener, or null to ignore errors.
	 */
	public BulkItemsRequest(int method, String url, String requestBody, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		super(method, url, requestBody, listener, errorListener);
	}

	/**
	 * Creates a new request.
	 *
	 * @param url           URL to fetch the JSON from
	 * @param listener      Listener to receive the JSON response
	 * @param errorListener Error listener, or null to ignore errors.
	 */
	public BulkItemsRequest(String url, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		super(Method.GET, url, null, listener, errorListener);
	}

	/**
	 * Creates a new request.
	 *
	 * @param method        the HTTP method to use
	 * @param url           URL to fetch the JSON from
	 * @param listener      Listener to receive the JSON response
	 * @param errorListener Error listener, or null to ignore errors.
	 */
	public BulkItemsRequest(int method, String url, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		super(method, url, null, listener, errorListener);
	}

	/**
	 * Creates a new request.
	 *
	 * @param method        the HTTP method to use
	 * @param url           URL to fetch the JSON from
	 * @param jsonRequest   A {@link JSONArray} to post with the request. Null is allowed and
	 *                      indicates no parameters will be posted along with request.
	 * @param listener      Listener to receive the JSON response
	 * @param errorListener Error listener, or null to ignore errors.
	 */
	public BulkItemsRequest(int method, String url, JSONArray jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener);
	}


	/**
	 * Creates a new request.
	 *
	 * @param method        the HTTP method to use
	 * @param url           URL to fetch the JSON from
	 * @param jsonRequest   A {@link JSONObject} to post with the request. Null is allowed and
	 *                      indicates no parameters will be posted along with request.
	 * @param listener      Listener to receive the JSON response
	 * @param errorListener Error listener, or null to ignore errors.
	 */
	public BulkItemsRequest(int method, String url, JSONObject jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener);
	}

	/**
	 * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
	 * <code>null</code>, <code>POST</code> otherwise.
	 * <p>
	 * see #MyjsonPostRequest(int, String, JSONArray, Listener, ErrorListener)
	 */
	public BulkItemsRequest(String url, JSONArray jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, listener, errorListener);
	}


	/**
	 * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
	 * <code>null</code>, <code>POST</code> otherwise.
	 * <p>
	 * see #MyjsonPostRequest(int, String, JSONObject, Listener, ErrorListener)
	 */
	public BulkItemsRequest(String url, JSONObject jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
		this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, listener, errorListener);
	}


	@Override
	protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
		try {
			String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
			return Response.success(new JSONArray(jsonString), HttpHeaderParser.parseCacheHeaders(response));
		} catch (UnsupportedEncodingException e) {
			return Response.error(new ParseError(e));
		} catch (JSONException je) {
			return Response.error(new ParseError(je));
		}
	}
}
Sample usage (I'm just generating random items for testing):
String TAG = "BulkItemsTest";
RequestQueue mRequestQueue = Volley.newRequestQueue(this);
org.json.JSONObject bulkItems = new JSONObject();
org.json.JSONArray ja = new JSONArray();
org.json.JSONObject jo;
try {
	for (int i = 0; i < 3; i++) {
		jo = new JSONObject();
		jo.put("name", IdUtils.getRandomString(6));
		jo.put("price", (long) random.nextInt(2000));
		jo.put("quantitySold", df.format(random.nextDouble() * 10));
		jo.put("unitName", "ea");
		ja.put(jo);
	}
	bulkItems.put("items", ja);

	String url = "https://apisandbox.dev.clover.com/v3/merchants/ ID>/orders/<Order ID>/bulk_line_items?access_token=<OAuth access_token>";

	BulkItemsRequest jsonObjReq = new BulkItemsRequest(url, bulkItems, new Response.Listener<JSONArray>() {
		@Override
		public void onResponse(JSONArray response) {
			Log.d(TAG, "Add bulk line items response was : " + response);
		}
	}, new Response.ErrorListener() {
		@Override
		public void onErrorResponse(VolleyError error) {
			Log.d(TAG, "That didn't work! : " + error.getMessage());
		}
	});
	// Add the request to the RequestQueue.
	mRequestQueue.add(jsonObjReq);
} catch (JSONException e) {
	e.printStackTrace();
}

Enjoy.
1 comment
10 |2000

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

keithryanwong avatar image keithryanwong commented ·

Thanks for posting this, @cdcilley!

0 Likes 0 ·

Welcome to the
Clover Developer Community