lib: json: fix arr_encode()

The JSON library doesn't properly encode arrays whose elements are of
object type. Fix that.

This fix avoids allocating a temporary descriptor on the stack, and
keeps the size of struct json_obj_descr unchanged, by preserving an
unintuitive size optimization made by the library.  See the comments
in the patch for more details.

Signed-off-by: Marti Bolivar <marti.bolivar@linaro.org>
diff --git a/lib/json/json.c b/lib/json/json.c
index 4d4ec6e..683bf53 100644
--- a/lib/json/json.c
+++ b/lib/json/json.c
@@ -697,13 +697,18 @@
 static int encode(const struct json_obj_descr *descr, const void *val,
 		  json_append_bytes_t append_bytes, void *data);
 
-static int arr_encode(const struct json_obj_descr *descr, const void *field,
-		      const void *val, json_append_bytes_t append_bytes,
-		      void *data)
+static int arr_encode(const struct json_obj_descr *elem_descr,
+		      const void *field, const void *val,
+		      json_append_bytes_t append_bytes, void *data)
 {
-	struct json_obj_descr elem_descr = { .type = descr->type };
-	ptrdiff_t elem_size = get_elem_size(descr);
-	size_t n_elem = *(size_t *)((char *)val + descr->offset);
+	ptrdiff_t elem_size = get_elem_size(elem_descr);
+	/*
+	 * NOTE: Since an element descriptor's offset isn't meaningful
+	 * (array elements occur at multiple offsets in `val'), we use
+	 * its space in elem_descr to store the offset to the field
+	 * containing the number of elements.
+	 */
+	size_t n_elem = *(size_t *)((char *)val + elem_descr->offset);
 	size_t i;
 	int ret;
 
@@ -713,7 +718,23 @@
 	}
 
 	for (i = 0; i < n_elem; i++) {
-		ret = encode(&elem_descr, field, append_bytes, data);
+		/*
+		 * Though "field" points at the next element in the
+		 * array which we need to encode, the value in
+		 * elem_descr->offset is actually the offset of the
+		 * length field in the "parent" struct containing the
+		 * array.
+		 *
+		 * To patch things up, we lie to encode() about where
+		 * the field is by exactly the amount it will offset
+		 * it. This is a size optimization for struct
+		 * json_obj_descr: the alternative is to keep a
+		 * separate field next to element_descr which is an
+		 * offset to the length field in the parent struct,
+		 * but that would add a size_t to every descriptor.
+		 */
+		ret = encode(elem_descr, (char *)field - elem_descr->offset,
+			     append_bytes, data);
 		if (ret < 0) {
 			return ret;
 		}