changeset 29994:eb768fc5e6b7

jsondecode.cc: Handle array of arrays and arrays of structs correctly (bug #60688) * libinterp/corefcn/jsondecode.cc (decode_object_array): Resize struct array if it has no fields. * libinterp/corefcn/jsondecode.cc (decode_array_of_arrays): Handle struct arrays correctly. * test/json/jsondecode_BIST.tst: New tests. Many thanks to an anonymous contributor.
author Kai T. Ohlhus <k.ohlhus@gmail.com>
date Wed, 18 Aug 2021 14:56:07 +0900
parents b5fae48ad807
children 4628ae890642
files libinterp/corefcn/jsondecode.cc test/json/jsondecode_BIST.tst
diffstat 2 files changed, 113 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/jsondecode.cc	Wed Aug 18 00:25:50 2021 -0400
+++ b/libinterp/corefcn/jsondecode.cc	Wed Aug 18 14:56:07 2021 +0900
@@ -239,13 +239,22 @@
   if (same_field_names)
     {
       octave_map struct_array;
-      Cell value (dim_vector (struct_cell.numel (), 1));
-      for (octave_idx_type i = 0; i < field_names.numel (); ++i)
+      dim_vector struct_array_dims = dim_vector (struct_cell.numel (), 1);
+
+      if (field_names.numel ())
         {
-          for (octave_idx_type k = 0; k < struct_cell.numel (); ++k)
-            value(k) = struct_cell(k).scalar_map_value ().getfield (field_names(i));
-          struct_array.assign (field_names(i), value);
+          Cell value (struct_array_dims);
+          for (octave_idx_type i = 0; i < field_names.numel (); ++i)
+            {
+              for (octave_idx_type k = 0; k < struct_cell.numel (); ++k)
+                value(k) = struct_cell(k).scalar_map_value ()
+                                         .getfield (field_names(i));
+              struct_array.assign (field_names(i), value);
+            }
         }
+      else
+        struct_array.resize (struct_array_dims, true);
+
       return struct_array;
     }
   else
@@ -286,6 +295,9 @@
 
   // Only arrays with sub-arrays of booleans and numericals will return NDArray
   bool is_bool = cell(0).is_bool_matrix ();
+  bool is_struct = cell(0).isstruct ();
+  string_vector field_names = is_struct ? cell(0).map_value ().fieldnames ()
+                                        : string_vector ();
   dim_vector sub_array_dims = cell(0).dims ();
   octave_idx_type sub_array_ndims = cell(0).ndims ();
   octave_idx_type cell_numel = cell.numel ();
@@ -302,6 +314,13 @@
       // return cell array
       if (cell(i).is_bool_matrix () != is_bool)
         return cell;
+      // If not struct arrays only, return cell array
+      if (cell(i).isstruct () != is_struct)
+        return cell;
+      // If struct arrays have different fields, return cell array
+      if (is_struct && (field_names.std_list ()
+                        != cell(i).map_value ().fieldnames ().std_list ()))
+        return cell;
     }
 
   // Calculate the dims of the output array
@@ -310,21 +329,55 @@
   array_dims(0) = cell_numel;
   for (auto i = 1; i < sub_array_ndims + 1; i++)
     array_dims(i) = sub_array_dims(i-1);
-  NDArray array (array_dims);
 
-  // Populate the array with specific order to generate MATLAB-identical output
-  octave_idx_type sub_array_numel = array.numel () / cell_numel;
-  for (octave_idx_type k = 0; k < cell_numel; ++k)
+  if (is_struct)
     {
-      NDArray sub_array_value = cell(k).array_value ();
-      for (octave_idx_type i = 0; i < sub_array_numel; ++i)
-        array(k + i*cell_numel) = sub_array_value(i);
-    }
+      octave_map struct_array;
+      array_dims.chop_trailing_singletons ();
+
+      if (field_names.numel ())
+        {
+          Cell value (array_dims);
+          octave_idx_type sub_array_numel = sub_array_dims.numel ();
 
-  if (is_bool)
-    return boolNDArray (array);
+          for (octave_idx_type j = 0; j < field_names.numel (); ++j)
+            {
+              // Populate the array with specific order to generate
+              // MATLAB-identical output.
+              for (octave_idx_type k = 0; k < cell_numel; ++k)
+                {
+                  Cell sub_array_value = cell(k).map_value ()
+                                                .getfield (field_names(j));
+                  for (octave_idx_type i = 0; i < sub_array_numel; ++i)
+                    value(k + i * cell_numel) = sub_array_value(i);
+                }
+              struct_array.assign (field_names(j), value);
+            }
+        }
+      else
+        struct_array.resize(array_dims, true);
+
+      return struct_array;
+    }
   else
-    return array;
+    {
+      NDArray array (array_dims);
+
+      // Populate the array with specific order to generate MATLAB-identical
+      // output.
+      octave_idx_type sub_array_numel = array.numel () / cell_numel;
+      for (octave_idx_type k = 0; k < cell_numel; ++k)
+        {
+          NDArray sub_array_value = cell(k).array_value ();
+          for (octave_idx_type i = 0; i < sub_array_numel; ++i)
+            array(k + i * cell_numel) = sub_array_value(i);
+        }
+
+      if (is_bool)
+        return boolNDArray (array);
+      else
+        return array;
+    }
 }
 
 //! Decodes any type of JSON arrays.  This function only serves as an interface
--- a/test/json/jsondecode_BIST.tst	Wed Aug 18 00:25:50 2021 -0400
+++ b/test/json/jsondecode_BIST.tst	Wed Aug 18 14:56:07 2021 +0900
@@ -555,6 +555,50 @@
 %! obs  = jsondecode (json, "ReplacementStyle", "underscore", "Prefix", "x_");
 %! assert (isequal (obs, exp));
 
+%%% Test 8: More tests from https://github.com/apjanke/octave-jsonstuff (bug #60688)
+
+%!testif HAVE_RAPIDJSON
+%! assert (jsondecode ('42'), 42);
+%! assert (jsondecode ('"foobar"'), "foobar");
+%! assert (jsondecode ('null'), []);
+%! assert (jsondecode ('[]'), []);
+%! assert (jsondecode ('[[]]'), {[]});
+%! assert (jsondecode ('[[[]]]'), {{[]}});
+%! assert (jsondecode ('[1, 2, 3]'), [1; 2; 3]);
+%! assert (jsondecode ('[1, 2, null]'), [1; 2; NaN]);
+%! assert (jsondecode ('[1, 2, "foo"]'), {1; 2; "foo"});
+%! assert (jsondecode ('{"foo": 42, "bar": "hello"}'), ...
+%!         struct("foo",42, "bar","hello"));
+%! assert (jsondecode ('[{"foo": 42, "bar": "hello"}, {"foo": 1.23, "bar": "world"}]'), ...
+%!         struct("foo", {42; 1.23}, "bar", {"hello"; "world"}));
+%! assert (jsondecode ('[1, 2]'), [1; 2]);
+%! assert (jsondecode ('[[1, 2]]'), [1 2]);
+%! assert (jsondecode ('[[[1, 2]]]'), cat(3, 1, 2));
+%! assert (jsondecode ('[[1, 2], [3, 4]]'), [1 2; 3 4]);
+%! assert (jsondecode ('[[[1, 2], [3, 4]]]'), cat(3, [1 3], [2 4]));
+%! assert (jsondecode ('[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]'), ...
+%!         cat(3, [1 3; 5 7], [2 4; 6 8]));
+%! assert (jsondecode ('{}'), struct);
+%! assert (jsondecode ('[{}]'), struct);
+%! assert (jsondecode ('[[{}]]'), struct);
+%! assert (jsondecode ('[{}, {}]'), [struct; struct]);
+%! assert (jsondecode ('[[{}, {}]]'), [struct struct]);
+%! assert (jsondecode ('[[[{}, {}]]]'), cat(3, struct, struct));
+%! assert (jsonencode (42), "42");
+%! assert (jsonencode ("foo"), '"foo"');
+%! assert (jsonencode ([1 2 3]), '[1,2,3]');
+%! assert (jsonencode (NaN), 'null');
+%! assert (jsonencode ([1 2 NaN]), '[1,2,null]');
+%! assert (jsonencode ({}), "[]");
+
+%%% Test 9: And even some more tests for #60688...
+
+%!testif HAVE_RAPIDJSON
+%! assert (jsondecode ('[[{"foo": 42}, {"foo": 1.23}], [{"foo": 12}, {"foo": "bar"}]]'), ...
+%!         struct("foo", {42 1.23; 12 "bar"}));
+%! assert (jsondecode ('[[{"foo": 42}, {"foo": 1.23}], [{"bar": 12}, {"foo": "bar"}]]'), ...
+%!         {struct("foo", {42; 1.23}); {struct("bar", 12); struct("foo", "bar")}});
+
 %% Check decoding of objects inside an object without using makeValidName
 %!testif HAVE_RAPIDJSON
 %! json = ['{"object": {"  hi 1   ": 1, "%string.array": 2,' ...