# HG changeset patch # User Kai T. Ohlhus # Date 1629266167 -32400 # Node ID eb768fc5e6b784136ca8b0f1f18b2dcc20599efe # Parent b5fae48ad807ff2d0f38071854bd21a357f6b2f9 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. diff -r b5fae48ad807 -r eb768fc5e6b7 libinterp/corefcn/jsondecode.cc --- 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 diff -r b5fae48ad807 -r eb768fc5e6b7 test/json/jsondecode_BIST.tst --- 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,' ...