# HG changeset patch # User Abdallah Elshamy # Date 1597330627 -32400 # Node ID 5da49e37a6c94bdb69d2bddbb705bfd85e7eeab9 # Parent 652a675c091629639ffbfbfe6b49223ecb2d7533 New functions jsondecode and jsonencode (bug #53100). * NEWS: Add to list of new functions. * configure.ac: Look for RapidJSON library. * libinterp/corefcn/jsondecode.cc: New function. * libinterp/corefcn/jsonencode.cc: New function. * libinterp/corefcn/module.mk: Add new functions to build system. * scripts/help/__unimplemented__.m: Remove the function. * test/json/jsondecodetest.tst: New test files. * test/json/jsonencodetest.tst: New test files. * test/json/module.mk: Add new functions to build system. * test/module.mk: Add new functions to build system. This is the result of GSoC 2020 by Abdallah Elshamy. diff -r 652a675c0916 -r 5da49e37a6c9 NEWS --- a/NEWS Mon Aug 03 11:13:26 2020 -0400 +++ b/NEWS Thu Aug 13 23:57:07 2020 +0900 @@ -47,6 +47,9 @@ and if a function uses varargout then the check is skipped for function outputs. +- As part of GSoC 2020, Abdallah K. Elshamy implemented the +`jsondecode` and `jsonencode` functions to read and write JSON data. + - The function `griddata` now implements the "v4" Biharmonic Spline Interpolation method. In adddition, the function now accepts 3-D inputs by passing the data to `griddata3`. @@ -133,6 +136,8 @@ * `getpixelposition` * `endsWith` +* `jsondecode` +* `jsonencode` * `listfonts` * `memory` * `rng` diff -r 652a675c0916 -r 5da49e37a6c9 configure.ac --- a/configure.ac Mon Aug 03 11:13:26 2020 -0400 +++ b/configure.ac Thu Aug 13 23:57:07 2020 +0900 @@ -1331,6 +1331,22 @@ ], [libpcre], [REQUIRED]) +### Check for RapidJSON header only library. + +AC_LANG_PUSH([C++]) +AC_CHECK_HEADER([rapidjson/rapidjson.h], [have_rapidjson=yes], [have_rapidjson=no + rapid_json_warning="RapidJSON library not found. Octave will not be able to read or write JSON files."]) +if test "$have_rapidjson" = yes; then + AC_CHECK_HEADER([rapidjson/cursorstreamwrapper.h], [have_rapidjson=yes], [have_rapidjson=no + rapid_json_warning="RapidJSON 1.1.0 or older found, but latest development version needed. Octave will not be able to read or write JSON files."]) +fi +if test "$have_rapidjson" = yes; then + AC_DEFINE(HAVE_RAPIDJSON, 1, [Define to 1 if RapidJSON is available.]) +else + OCTAVE_CONFIGURE_WARNING([rapid_json_warning]) +fi +AC_LANG_POP([C++]) + ### Check for readline library. OCTAVE_ENABLE_READLINE diff -r 652a675c0916 -r 5da49e37a6c9 libinterp/corefcn/jsondecode.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/jsondecode.cc Thu Aug 13 23:57:07 2020 +0900 @@ -0,0 +1,572 @@ +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2020 The Octave Project Developers +// +// See the file COPYRIGHT.md in the top-level directory of this +// distribution or . +// +// This file is part of Octave. +// +// Octave is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Octave is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Octave; see the file COPYING. If not, see +// . +// +//////////////////////////////////////////////////////////////////////// + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#include "defun.h" +#include "error.h" +#include "errwarn.h" +#include "ovl.h" +#include "parse.h" + +#if defined (HAVE_RAPIDJSON) +# include +# include +#endif + +#if defined (HAVE_RAPIDJSON) + +octave_value +decode (const rapidjson::Value& val, const octave_value_list& options); + +//! Checks if two instances of @ref string_vector are equal. +//! +//! @param a The first @ref string_vector. +//! @param b The second @ref string_vector. +//! +//! @return @c bool that indicates if they are equal. +//! +//! @b Example: +//! +//! @code{.cc} +//! string_vector a ({"foo", "bar"}); +//! string_vector b ({"foo", "baz"}); +//! bool is_equal = equals (a, b); +//! @endcode + +bool +equals (const string_vector& a, const string_vector& b) +{ + // FIXME: move to string_vector class + octave_idx_type n = a.numel (); + if (n != b.numel ()) + return false; + for (octave_idx_type i = 0; i < n; ++i) + if (a(i) != b(i)) + return false; + + return true; +} + +//! Decodes a numerical JSON value into a scalar number. +//! +//! @param val JSON value that is guaranteed to be a numerical value. +//! +//! @return @ref octave_value that contains the numerical value of @p val. +//! +//! @b Example: +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("123"); +//! octave_value num = decode_number (d); +//! @endcode + +octave_value +decode_number (const rapidjson::Value& val) +{ + if (val.IsUint ()) + return octave_value (val.GetUint ()); + else if (val.IsInt ()) + return octave_value (val.GetInt ()); + else if (val.IsUint64 ()) + return octave_value (val.GetUint64 ()); + else if (val.IsInt64 ()) + return octave_value (val.GetInt64 ()); + else if (val.IsDouble ()) + return octave_value (val.GetDouble ()); + else + error ("jsondecode.cc: Unidentified type."); +} + +//! Decodes a JSON object into a scalar struct. +//! +//! @param val JSON value that is guaranteed to be a JSON object. +//! @param options @c ReplacementStyle and @c Prefix options with their values. +//! +//! @return @ref octave_value that contains the equivalent scalar struct of @p val. +//! +//! @b Example: +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("{\"a\": 1, \"b\": 2}"); +//! octave_value struct = decode_object (d, octave_value_list ()); +//! @endcode + +octave_value +decode_object (const rapidjson::Value& val, const octave_value_list& options) +{ + octave_scalar_map retval; + for (const auto& pair : val.GetObject ()) + { + std::string fcn_name = "matlab.lang.makeValidName"; + octave_value_list args = octave_value_list (pair.name.GetString ()); + args.append (options); + std::string validName = octave::feval (fcn_name,args)(0).string_value (); + retval.assign (validName, decode (pair.value, options)); + } + return octave_value (retval); +} + +//! Decodes a JSON array that contains only numerical or null values +//! into an NDArray. +//! +//! @param val JSON value that is guaranteed to be a numeric array. +//! +//! @return @ref octave_value that contains the equivalent NDArray of @p val. +//! +//! @b Example: +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[1, 2, 3, 4]"); +//! octave_value numeric_array = decode_numeric_array (d); +//! @endcode + +octave_value +decode_numeric_array (const rapidjson::Value& val) +{ + NDArray retval (dim_vector (val.Size (), 1)); + octave_idx_type index = 0; + for (const auto& elem : val.GetArray ()) + retval(index++) = elem.IsNull () ? octave_NaN + : decode_number (elem).double_value (); + return retval; +} + +//! Decodes a JSON array that contains only boolean values into a boolNDArray. +//! +//! @param val JSON value that is guaranteed to be a boolean array. +//! +//! @return @ref octave_value that contains the equivalent boolNDArray of @p val. +//! +//! @b Example: +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[true, false, true]"); +//! octave_value boolean_array = decode_boolean_array (d); +//! @endcode + +octave_value +decode_boolean_array (const rapidjson::Value& val) +{ + boolNDArray retval (dim_vector (val.Size (), 1)); + octave_idx_type index = 0; + for (const auto& elem : val.GetArray ()) + retval(index++) = elem.GetBool (); + return retval; +} + +//! Decodes a JSON array that contains different types +//! or string values only into a Cell. +//! +//! @param val JSON value that is guaranteed to be a mixed or string array. +//! @param options @c ReplacementStyle and @c Prefix options with their values. +//! +//! @return @ref octave_value that contains the equivalent Cell of @p val. +//! +//! @b Example (decoding a string array): +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[\"foo\", \"bar\", \"baz\"]"); +//! octave_value cell = decode_string_and_mixed_array (d, octave_value_list ()); +//! @endcode +//! +//! @b Example (decoding a mixed array): +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[\"foo\", 123, true]"); +//! octave_value cell = decode_string_and_mixed_array (d, octave_value_list ()); +//! @endcode + +octave_value +decode_string_and_mixed_array (const rapidjson::Value& val, + const octave_value_list& options) +{ + Cell retval (dim_vector (val.Size (), 1)); + octave_idx_type index = 0; + for (const auto& elem : val.GetArray ()) + retval(index++) = decode (elem, options); + return retval; +} + +//! Decodes a JSON array that contains only objects into a Cell or a struct array +//! depending on the similarity of the objects' keys. +//! +//! @param val JSON value that is guaranteed to be an object array. +//! @param options @c ReplacementStyle and @c Prefix options with their values. +//! +//! @return @ref octave_value that contains the equivalent Cell +//! or struct array of @p val. +//! +//! @b Example (returns a struct array): +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[{\"a\":1,\"b\":2},{\"a\":3,\"b\":4}]"); +//! octave_value object_array = decode_object_array (d, octave_value_list ()); +//! @endcode +//! +//! @b Example (returns a Cell): +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[{\"a\":1,\"b\":2},{\"b\":3,\"a\":4}]"); +//! octave_value object_array = decode_object_array (d, octave_value_list ()); +//! @endcode + +octave_value +decode_object_array (const rapidjson::Value& val, + const octave_value_list& options) +{ + Cell struct_cell = decode_string_and_mixed_array (val, options).cell_value (); + string_vector field_names = struct_cell(0).scalar_map_value ().fieldnames (); + bool same_field_names = 1; + for (octave_idx_type i = 1; i < struct_cell.numel (); ++i) + if (! equals (field_names, struct_cell(i).scalar_map_value ().fieldnames ())) + { + same_field_names = 0; + break; + } + 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) + { + 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); + } + return octave_value (struct_array); + } + else + return struct_cell; +} + +//! Decodes a JSON array that contains only arrays into a Cell or an NDArray +//! depending on the dimensions and the elements' type of the sub arrays. +//! +//! @param val JSON value that is guaranteed to be an array of arrays. +//! @param options @c ReplacementStyle and @c Prefix options with their values. +//! +//! @return @ref octave_value that contains the equivalent Cell +//! or NDArray of @p val. +//! +//! @b Example (returns an NDArray): +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[[1, 2], [3, 4]]"); +//! octave_value array = decode_array_of_arrays (d, octave_value_list ()); +//! @endcode +//! +//! @b Example (returns a Cell): +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[[1, 2], [3, 4, 5]]"); +//! octave_value cell = decode_array_of_arrays (d, octave_value_list ()); +//! @endcode + +octave_value +decode_array_of_arrays (const rapidjson::Value& val, + const octave_value_list& options) +{ + // Some arrays should be decoded as NDArrays and others as cell arrays + Cell cell = decode_string_and_mixed_array(val, options).cell_value (); + // Only arrays with sub arrays of booleans and numericals will return NDArray + bool is_bool = cell(0).is_bool_matrix (); + dim_vector sub_array_dims = cell(0).dims (); + octave_idx_type sub_array_ndims = cell(0).ndims (); + octave_idx_type cell_numel = cell.numel (); + for (octave_idx_type i = 0; i < cell_numel; ++i) + { + // If one element is cell return the cell array as at least one of + // the sub arrays area either an array of: strings, objects or mixed array + if (cell(i).iscell ()) + return cell; + // If not the same dim of elements or dim = 0 return cell array + if (cell(i).dims () != sub_array_dims || sub_array_dims == dim_vector ()) + return cell; + // If not numeric sub arrays only or bool + // sub arrays only return cell array + if(cell(i).is_bool_matrix () != is_bool) + return cell; + } + // Calculate the dims of the output array + dim_vector array_dims; + array_dims.resize (sub_array_ndims + 1); + 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 array_index = 0; + for (octave_idx_type i = 0; i < array.numel () / cell_numel; ++i) + for (octave_idx_type k = 0; k < cell_numel; ++k) + array(array_index++) = cell(k).array_value ()(i); + return array; +} + +//! Decodes any type of JSON arrays. This function only serves as an interface +//! by choosing which function to call from the previous functions. +//! +//! @param val JSON value that is guaranteed to be an array. +//! @param options @c ReplacementStyle and @c Prefix options with their values. +//! +//! @return @ref octave_value that contains the output of decoding @p val. +//! +//! @b Example: +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[[1, 2], [3, 4, 5]]"); +//! octave_value array = decode_array (d, octave_value_list ()); +//! @endcode + +octave_value +decode_array (const rapidjson::Value& val, const octave_value_list& options) +{ + // Handle empty arrays + if (val.Empty ()) + return NDArray (dim_vector (0,0)); + + // Compare with other elements to know if the array has multiple types + rapidjson::Type array_type = val[0].GetType (); + // Check if the array is numeric and if it has multible types + bool same_type = 1, is_numeric = 1; + for (const auto& elem : val.GetArray ()) + { + rapidjson::Type current_elem_type = elem.GetType (); + if (is_numeric && ! (current_elem_type == rapidjson::kNullType + || current_elem_type == rapidjson::kNumberType)) + is_numeric = 0; + if (same_type && (current_elem_type != array_type)) + // RapidJSON doesn't have kBoolean Type it has kTrueType and kFalseType + if (! ((current_elem_type == rapidjson::kTrueType + && array_type == rapidjson::kFalseType) + || (current_elem_type == rapidjson::kFalseType + && array_type == rapidjson::kTrueType))) + same_type = 0; + } + if (is_numeric) + return decode_numeric_array (val); + if (same_type && (array_type != rapidjson::kStringType)) + { + if (array_type == rapidjson::kTrueType + || array_type == rapidjson::kFalseType) + return decode_boolean_array (val); + else if (array_type == rapidjson::kObjectType) + return decode_object_array (val, options); + else if (array_type == rapidjson::kArrayType) + return decode_array_of_arrays (val, options); + else + error ("jsondecode.cc: Unidentified type."); + } + else + return decode_string_and_mixed_array (val, options); +} + +//! Decodes any JSON value. This function only serves as an interface +//! by choosing which function to call from the previous functions. +//! +//! @param val JSON value. +//! @param options @c ReplacementStyle and @c Prefix options with their values. +//! +//! @return @ref octave_value that contains the output of decoding @p val. +//! +//! @b Example: +//! +//! @code{.cc} +//! rapidjson::Document d; +//! d.Parse ("[{\"a\":1,\"b\":2},{\"b\":3,\"a\":4}]"); +//! octave_value value = decode (d, octave_value_list ()); +//! @endcode + +octave_value +decode (const rapidjson::Value& val, const octave_value_list& options) +{ + if (val.IsBool ()) + return val.GetBool (); + else if (val.IsNumber ()) + return decode_number (val); + else if (val.IsString ()) + return val.GetString (); + else if (val.IsObject ()) + return decode_object (val, options); + else if (val.IsNull ()) + return NDArray (dim_vector (0,0)); + else if (val.IsArray ()) + return decode_array (val, options); + else + error ("jsondecode.cc: Unidentified type."); +} + +#endif + +DEFUN (jsondecode, args, , + doc: /* -*- texinfo -*- +@deftypefn {} {@var{object} =} jsondecode (@var{json}) +@deftypefnx {} {@var{object} =} jsondecode (@var{json}, "ReplacementStyle", @var{rs}) +@deftypefnx {} {@var{object} =} jsondecode (@var{json}, "Prefix", @var{pfx}) +@deftypefnx {} {@var{object} =} jsondecode (@var{json}, @dots{}) + +Decode text that is formatted in JSON. + +The input @var{json} is a string that contains JSON text. +The output @var{object} is an Octave object that contains the result +of decoding @var{json}. + +For more information about the options @qcode{"ReplacementStyle"} and +@qcode{"Prefix"}, see @ref{matlab.lang.makeValidName}. + +-NOTE: It is not guaranteed to get the same JSON text if you decode +and then encode it as some names may change by @ref{matlab.lang.makeValidName}. + +This table shows the conversions from JSON data types to Octave data types: + +@table @asis +@item @qcode{"Boolean"} +Scalar @qcode{"logical"} + +@item @qcode{"Number"} +Scalar @qcode{"double"} + +@item @qcode{"String"} +@qcode{"Vector"} of chars + +@item JSON @qcode{"Object"} +Scalar @qcode{"struct"} (field names of the struct may be different from +the keys of the JSON object due to @ref{matlab.lang.makeValidName}) + +@item @qcode{"Array"} of different data types +@qcode{"Cell"} array + +@item @qcode{"Array"} of booleans +@qcode{"Array"} of logicals + +@item @qcode{"Array"} of numbers +@qcode{"Array"} of doubles + +@item @qcode{"Array"} of strings +@qcode{"Cell"} array of vectors of chars + +@item @qcode{"Array"} of JSON objects (All objects have the same field names) +@qcode{"Struct array"} + +@item @qcode{"Array"} of JSON objects (Objects have different field names) +@qcode{"Cell"} array of scalar structs + +@item @qcode{"null"} inside a numeric array +@qcode{"NaN"} + +@item @qcode{"null"} inside a non-numeric array +Empty @qcode{"Array"} of doubles (@qcode{"[]"}) +@end table + +Examples: + +@example +@group +jsondecode ('[1, 2, null, 3]') + @result{} 1 2 NaN 3 +@end group + +@group +jsondecode ('["foo", "bar", ["foo", "bar"]]') + @result{} ans = + { + [1,1] = foo + [2,1] = bar + [3,1] = + { + [1,1] = foo + [2,1] = bar + } + + } +@end group + +@group +jsondecode ('{"nu#m#ber": 7, "s#tr#ing": "hi"}', 'ReplacementStyle', 'delete') + @result{} scalar structure containing the fields: + number = 7 + string = hi +@end group + +@group +jsondecode ('{"1": "one", "2": "two"}', 'Prefix', 'm_') + @result{} scalar structure containing the fields: + m_1 = one + m_2 = two +@end group +@end example + +@seealso{jsonencode, matlab.lang.makeValidName} +@end deftypefn */) +{ +#if defined (HAVE_RAPIDJSON) + + int nargin = args.length (); + // makeValidName options must be in pairs + // The number of arguments must be odd + if (! (nargin % 2)) + print_usage (); + + if(! args(0).is_string ()) + error ("jsondecode: The input must be a character string"); + + std::string json = args (0).string_value (); + rapidjson::Document d; + // DOM is chosen instead of SAX as SAX publishes events to a handler that + // decides what to do depending on the event only. This will cause a problem + // in decoding JSON arrays as the output may be an array or a cell and that + // doesn't only depend on the event (startArray) but also on the types of + // the elements inside the array + d.Parse (json.c_str ()); + + if (d.HasParseError ()) + error("jsondecode: Parse error at offset %u: %s\n", + d.GetErrorOffset (), + rapidjson::GetParseError_En (d.GetParseError ())); + return decode (d, args.slice (1, nargin-1)); + +#else + + octave_unused_parameter (args); + + err_disabled_feature ("jsondecode", + "RapidJSON is required for JSON encoding\\decoding"); + +#endif +} diff -r 652a675c0916 -r 5da49e37a6c9 libinterp/corefcn/jsonencode.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/jsonencode.cc Thu Aug 13 23:57:07 2020 +0900 @@ -0,0 +1,632 @@ +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2020 The Octave Project Developers +// +// See the file COPYRIGHT.md in the top-level directory of this +// distribution or . +// +// This file is part of Octave. +// +// Octave is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Octave is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Octave; see the file COPYING. If not, see +// . +// +//////////////////////////////////////////////////////////////////////// + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#include "builtin-defun-decls.h" +#include "defun.h" +#include "error.h" +#include "errwarn.h" +#include "ovl.h" +#include "oct-string.h" + +#if defined (HAVE_RAPIDJSON) +# include +# include +# include +#endif + +#if defined (HAVE_RAPIDJSON) + +//! Encodes a scalar Octave value into a numerical JSON value. +//! +//! @param writer RapidJSON's writer that is responsible for generating json. +//! @param obj scalar Octave value. +//! @param ConvertInfAndNaN @c bool that converts @c Inf and @c NaN to @c null. +//! +//! @b Example: +//! +//! @code{.cc} +//! octave_value obj (7); +//! encode_numeric (writer, obj,true); +//! @endcode + +template void +encode_numeric (T& writer, const octave_value& obj, const bool& ConvertInfAndNaN) +{ + double value = obj.scalar_value (); + if (obj.is_bool_scalar ()) + writer.Bool (obj.bool_value ()); + // Any numeric input from the interpreter will be in double type so in order + // to detect ints, we will check if the floor of the input and the input are + // equal using fabs(A - B) < epsilon method as it is more accurate. + // If value > 999999, MATLAB will encode it in scientific notation (double) + else if (fabs (floor (value) - value) < std::numeric_limits::epsilon () + && value <= 999999 && value >= -999999) + writer.Int64 (value); + // NA values doesn't exist in MATLAB, so I will decode it as null instead + else if (((octave::math::isnan (value) || std::isinf (value) + || std::isinf (-value)) && ConvertInfAndNaN) + || obj.isna ().bool_value ()) + writer.Null (); + else if (obj.is_double_type ()) + writer.Double (value); + else + error ("jsonencode: Unsupported type."); +} + +//! Encodes character vectors and arrays into JSON strings. +//! +//! @param writer RapidJSON's writer that is responsible for generating json. +//! @param obj character vectors or character arrays. +//! @param original_dims The original dimensions of the array being encoded. +//! @param level The level of recursion for the function. +//! +//! @b Example: +//! +//! @code{.cc} +//! octave_value obj ("foo"); +//! encode_string (writer, obj,true); +//! @endcode + +template void +encode_string (T& writer, const octave_value& obj, + const dim_vector& original_dims, int level = 0) +{ + charNDArray array = obj.char_array_value (); + if (array.isempty ()) + writer.String (""); + else if (array.isvector ()) + { + // Handle the special case when the input is a vector with more than + // 2 dimensions (e.g. cat (8, ['a'], ['c'])). In this case, we don't + // split the inner vectors of the input. we merge them into one. + if (level == 0) + { + std::string char_vector = ""; + for (octave_idx_type i = 0; i < array.numel (); ++i) + char_vector += array(i); + writer.String (char_vector.c_str ()); + } + else + for (octave_idx_type i = 0; i < array.numel () / original_dims(1); ++i) + { + std::string char_vector = ""; + for (octave_idx_type k = 0; k < original_dims(1); ++k) + char_vector += array(i * original_dims(1) + k); + writer.String (char_vector.c_str ()); + } + } + else + { + octave_idx_type idx; + octave_idx_type ndims = array.ndims (); + dim_vector dims = array.dims (); + + // In this case, we already have a vector. So, we transform it to 2-D + // vector in order to be detected by "isvector" in the recursive call + if (dims.num_ones () == ndims - 1) + { + // Handle the special case when the input is a vector with more than + // 2 dimensions (e.g. cat (8, ['a'], ['c'])). In this case, we don't + // add dimension brackets and treat it as if it is a vector + if (level != 0) + // Place an opening and a closing bracket (represents a dimension) + // for every dimension that equals 1 till we reach the 2-D vector + for (int i = level; i < ndims - 1; ++i) + writer.StartArray (); + + encode_string (writer, array.as_row (), original_dims, level); + + if (level != 0) + for (int i = level; i < ndims - 1; ++i) + writer.EndArray (); + } + else + { + // We place an opening and a closing bracket for each dimension + // that equals 1 to preserve the number of dimensions when decoding + // the array after encoding it. + if (original_dims (level) == 1 && level != 1) + { + writer.StartArray (); + encode_string (writer, array, original_dims, level + 1); + writer.EndArray (); + } + else + { + // The second dimension contains the number of the chars in + // the char vector. We want to treat them as a one object, + // so we replace it with 1 + dims(1) = 1; + + for (idx = 0; idx < ndims; ++idx) + if (dims(idx) != 1) + break; + // Create the dimensions that will be used to call "num2cell" + // We called "num2cell" to divide the array to smaller sub arrays + // in order to encode it recursively. + // The recursive encoding is necessary to support encoding of + // higher-dimensional arrays. + RowVector conversion_dims; + conversion_dims.resize (ndims - 1); + for (octave_idx_type i = 0; i < idx; ++i) + conversion_dims(i) = i + 1; + for (octave_idx_type i = idx ; i < ndims - 1; ++i) + conversion_dims(i) = i + 2; + + octave_value_list args (obj); + args.append (conversion_dims); + + Cell sub_arrays = Fnum2cell (args)(0).cell_value (); + + writer.StartArray (); + + for (octave_idx_type i = 0; i < sub_arrays.numel (); ++i) + encode_string (writer, sub_arrays(i), original_dims, + level + 1); + + writer.EndArray (); + } + } + } +} + +//! Encodes a struct Octave value into a JSON object or a JSON array depending +//! on the type of the struct (scalar struct or struct array.) +//! +//! @param writer RapidJSON's writer that is responsible for generating json. +//! @param obj struct Octave value. +//! @param ConvertInfAndNaN @c bool that converts @c Inf and @c NaN to @c null. +//! +//! @b Example: +//! +//! @code{.cc} +//! octave_value obj (octave_map ()); +//! encode_struct (writer, obj,true); +//! @endcode + +template void +encode_struct (T& writer, const octave_value& obj, const bool& ConvertInfAndNaN) +{ + octave_map struct_array = obj.map_value (); + octave_idx_type numel = struct_array.numel (); + string_vector keys = struct_array.keys (); + + if (numel > 1) + writer.StartArray (); + + for (octave_idx_type i = 0; i < numel; ++i) + { + writer.StartObject (); + for (octave_idx_type k = 0; k < keys.numel (); ++k) + { + writer.Key (keys(k).c_str ()); + encode (writer, struct_array(i).getfield (keys(k)), ConvertInfAndNaN); + } + writer.EndObject (); + } + + if (numel > 1) + writer.EndArray (); +} + +//! Encodes a Cell Octave value into a JSON array +//! +//! @param writer RapidJSON's writer that is responsible for generating json. +//! @param obj Cell Octave value. +//! @param ConvertInfAndNaN @c bool that converts @c Inf and @c NaN to @c null. +//! +//! @b Example: +//! +//! @code{.cc} +//! octave_value obj (cell ()); +//! encode_cell (writer, obj,true); +//! @endcode + +template void +encode_cell (T& writer, const octave_value& obj, const bool& ConvertInfAndNaN) +{ + Cell cell = obj.cell_value (); + + writer.StartArray (); + + for (octave_idx_type i = 0; i < cell.numel (); ++i) + encode (writer, cell(i), ConvertInfAndNaN); + + writer.EndArray (); +} + +//! Encodes a numeric or logical Octave array into a JSON array +//! +//! @param writer RapidJSON's writer that is responsible for generating json. +//! @param obj numeric or logical Octave array. +//! @param ConvertInfAndNaN @c bool that converts @c Inf and @c NaN to @c null. +//! @param original_dims The original dimensions of the array being encoded. +//! @param level The level of recursion for the function. +//! +//! @b Example: +//! +//! @code{.cc} +//! octave_value obj (NDArray ()); +//! encode_array (writer, obj,true); +//! @endcode + +template void +encode_array (T& writer, const octave_value& obj, const bool& ConvertInfAndNaN, + const dim_vector& original_dims, int level = 0) +{ + NDArray array = obj.array_value (); + if (array.isempty ()) + { + writer.StartArray (); + writer.EndArray (); + } + else if (array.isvector ()) + { + writer.StartArray (); + for (octave_idx_type i = 0; i < array.numel (); ++i) + { + if (obj.islogical ()) + encode_numeric (writer, bool (array(i)), ConvertInfAndNaN); + else + encode_numeric (writer, array(i), ConvertInfAndNaN); + } + writer.EndArray (); + } + else + { + octave_idx_type idx; + octave_idx_type ndims = array.ndims (); + dim_vector dims = array.dims (); + + // In this case, we already have a vector. So, we transform it to 2-D + // vector in order to be detected by "isvector" in the recursive call + if (dims.num_ones () == ndims - 1) + { + // Handle the special case when the input is a vector with more than + // 2 dimensions (e.g. ones ([1 1 1 1 1 6])). In this case, we don't + // add dimension brackets and treat it as if it is a vector + if (level != 0) + // Place an opening and a closing bracket (represents a dimension) + // for every dimension that equals 1 till we reach the 2-D vector + for (int i = level; i < ndims - 1; ++i) + writer.StartArray (); + + encode_array (writer, array.as_row (), ConvertInfAndNaN, + original_dims); + + if (level != 0) + for (int i = level; i < ndims - 1; ++i) + writer.EndArray (); + } + else + { + // We place an opening and a closing bracket for each dimension + // that equals 1 to preserve the number of dimensions when decoding + // the array after encoding it. + if (original_dims (level) == 1) + { + writer.StartArray (); + encode_array (writer, array, ConvertInfAndNaN, + original_dims, level + 1); + writer.EndArray (); + } + else + { + for (idx = 0; idx < ndims; ++idx) + if (dims(idx) != 1) + break; + + // Create the dimensions that will be used to call "num2cell" + // We called "num2cell" to divide the array to smaller sub arrays + // in order to encode it recursively. + // The recursive encoding is necessary to support encoding of + // higher-dimensional arrays. + RowVector conversion_dims; + conversion_dims.resize (ndims - 1); + for (octave_idx_type i = 0; i < idx; ++i) + conversion_dims(i) = i + 1; + for (octave_idx_type i = idx ; i < ndims - 1; ++i) + conversion_dims(i) = i + 2; + + octave_value_list args (obj); + args.append (conversion_dims); + + Cell sub_arrays = Fnum2cell (args)(0).cell_value (); + + writer.StartArray (); + + for (octave_idx_type i = 0; i < sub_arrays.numel (); ++i) + encode_array (writer, sub_arrays(i), ConvertInfAndNaN, + original_dims, level + 1); + + writer.EndArray (); + } + } + } +} + +//! Encodes any Octave object. This function only serves as an interface +//! by choosing which function to call from the previous functions. +//! +//! @param writer RapidJSON's writer that is responsible for generating json. +//! @param obj any @ref octave_value that is supported. +//! @param ConvertInfAndNaN @c bool that converts @c Inf and @c NaN to @c null. +//! +//! @b Example: +//! +//! @code{.cc} +//! octave_value obj (true); +//! encode (writer, obj,true); +//! @endcode + +template void +encode (T& writer, const octave_value& obj, const bool& ConvertInfAndNaN) +{ + if (obj.is_real_scalar ()) + encode_numeric (writer, obj, ConvertInfAndNaN); + // As I checked for scalars, this will detect numeric & logical arrays + else if (obj.isnumeric () || obj.islogical ()) + encode_array (writer, obj, ConvertInfAndNaN, obj.dims ()); + else if (obj.is_string ()) + encode_string (writer, obj, obj.dims ()); + else if (obj.isstruct ()) + encode_struct (writer, obj, ConvertInfAndNaN); + else if (obj.iscell ()) + encode_cell (writer, obj, ConvertInfAndNaN); + else if (obj.class_name () == "containers.Map") + // To extract the data in containers.Map, Convert it to a struct. + // The struct will have a "map" field that its value is a struct that + // contains the desired data. + // In order to convert it we will need to disable the + // "Octave:classdef-to-struct" warning and re-enable it. + { + set_warning_state ("Octave:classdef-to-struct", "off"); + encode_struct (writer, obj.scalar_map_value ().getfield ("map"), + ConvertInfAndNaN); + set_warning_state ("Octave:classdef-to-struct", "on"); + } + else if (obj.isobject ()) + { + set_warning_state ("Octave:classdef-to-struct", "off"); + encode_struct (writer, obj.scalar_map_value (), ConvertInfAndNaN); + set_warning_state ("Octave:classdef-to-struct", "on"); + } + else + error ("jsonencode: Unsupported type."); +} + +#endif + +DEFUN (jsonencode, args, , + doc: /* -*- texinfo -*- +@deftypefn {} {@var{json} =} jsonencode (@var{object}) +@deftypefnx {} {@var{json} =} jsonencode (@var{object}, "ConvertInfAndNaN", @var{conv}) +@deftypefnx {} {@var{json} =} jsonencode (@var{object}, "PrettyWriter", @var{pretty}) +@deftypefnx {} {@var{json} =} jsonencode (@var{object}, @dots{}) + +Encode Octave's data types into JSON text. + +The input @var{object} is the Octave's object we want to encode. +The output @var{json} is the JSON text that contains the result +of encoding @var{object}. + +If the value of the option @qcode{"ConvertInfAndNaN"} is true, @qcode{"NaN"}, +@qcode{"Inf"} and @qcode{"-Inf"} values will be converted to @qcode{"null"} +value in the output. If it is false, they will remain with their +original values. The default value for this option is true. + +If the value of the option @qcode{"PrettyWriter"} is true, the output text will +have indentations and line feeds. If it is false, the output will be condensed +and without any white-spaces. The default value for this option is false. + +-NOTES: +@itemize @bullet +@item +Complex numbers are not supported. + +@item +@qcode{"classdef"} objects and @qcode{"containers.Map"} objects are converted +into structs then encoded as structs. + +@item +To preserve the escape characters (e.g. "\n"), use double-quote strings. + +@item +It is not guaranteed to get the same dimensions for arrays if you encode +and then decode it. For example, If you encoded a row vector then decoded it, +you will get a column vector. + +@item +It is not guaranteed to get the same data type if you encode and then decode +an Octave value as Octave supports more data types than JSON. For example, If +you encoded an @qcode{"int32"} then decoded it, you will get a @qcode{"double"}. +@end itemize + + +This table shows the conversions from Octave data types to JSON data types: + +@table @asis +@item Scalar @qcode{"logical"} +@qcode{"Boolean"} + +@item @qcode{"NaN"}, @qcode{"Inf"}, @qcode{"-Inf"} +@qcode{"null"} + +@item Scalar numeric +@qcode{"Number"} + +@item @qcode{"Numeric vector"} +@qcode{"Numeric array"} + +@item @qcode{"Numeric array"} +Nested @qcode{"numeric array"} + +@item @qcode{"Logical vector"} +@qcode{"Boolean array"} + +@item @qcode{"Logical array"} +Nested @qcode{"boolean array"} + +@item @qcode{"Char vector"} +@qcode{"String"} + +@item @qcode{"Char array"} +Nested @qcode{"string array"} + +@item Scalar @qcode{"cell"} +@qcode{"array"} with a single element + +@item @qcode{"Cell vector"} +@qcode{"Array"} + +@item @qcode{"Cell array"} +single dimensional @qcode{"array"} + +@item Scalar @qcode{"struct"} +@qcode{"JSON Object"} + +@item @qcode{"Struct vector"} +@qcode{"JSON objects array"} + +@item @qcode{"Struct array"} +Nested @qcode{"JSON objects array"} + +@item @qcode{"classdef"} objects +@qcode{"JSON object"} + +@item @qcode{"containers.Map"} +@qcode{"JSON object"} +@end table + +Examples: + +@example +@group +jsonencode ([1 NaN; 3 4]) +@result{} [[1,null],[3,4]] +@end group + +@group +jsonencode ([1 NaN; 3 4], "ConvertInfAndNaN", false) +@result{} [[1,NaN],[3,4]] +@end group + +@group +jsonencode ([true; false], "ConvertInfAndNaN", false, "PrettyWriter", true) +@result{} ans = [ + true, + false + ] +@end group + +@group +jsonencode (['foo', 'bar'; 'foo', 'bar']) +@result{} ["foobar","foobar"] +@end group + +@group +jsonencode (struct ('a', Inf, 'b', [], 'c', struct ())) +@result{} {"a":null,"b":[],"c":{}} +@end group + +@group +jsonencode (struct ('structarray', struct ('a', {1; 3}, 'b', {2; 4}))) +@result{} {"structarray":[{"a":1,"b":2},{"a":3,"b":4}]} +@end group + +@group +jsonencode ({'foo'; 'bar'; {'foo'; 'bar'}}) +@result{} ["foo","bar",["foo","bar"]] +@end group + +@group +jsonencode (containers.Map({'foo'; 'bar'; 'baz'}, [1, 2, 3])) +@result{} {"bar":2,"baz":3,"foo":1} +@end group +@end example + +@seealso{jsondecode} +@end deftypefn */) +{ +#if defined (HAVE_RAPIDJSON) + + int nargin = args.length (); + // jsonencode has two options 'ConvertInfAndNaN' and 'PrettyWriter' + if (! (nargin == 1 || nargin == 3 || nargin == 5)) + print_usage (); + + // Initialize options with their default values + bool ConvertInfAndNaN = true; + bool PrettyWriter = false; + + for (octave_idx_type i = 1; i < nargin; ++i) + { + if (! args(i).is_string ()) + error ("jsonencode: Option must be character vector"); + if (! args(i+1).is_bool_scalar ()) + error ("jsonencode: Value for options must be logical scalar"); + + std::string option_name = args(i++).string_value (); + if (octave::string::strcmpi(option_name, "ConvertInfAndNaN")) + ConvertInfAndNaN = args(i).bool_value (); + else if (octave::string::strcmpi(option_name, "PrettyWriter")) + PrettyWriter = args(i).bool_value (); + else + error ("jsonencode: Valid options are \'ConvertInfAndNaN\'" + " and \'PrettyWriter\'"); + } + rapidjson::StringBuffer json; + if (PrettyWriter) + // In order to use the "PrettyWriter" option, you must use the development + // version of RapidJSON. The release causes an error in compilation. + { + rapidjson::PrettyWriter, + rapidjson::UTF8<>, rapidjson::CrtAllocator, + rapidjson::kWriteNanAndInfFlag> writer (json); + encode (writer, args(0), ConvertInfAndNaN); + } + else + { + rapidjson::Writer, + rapidjson::UTF8<>, rapidjson::CrtAllocator, + rapidjson::kWriteNanAndInfFlag> writer (json); + encode (writer, args(0), ConvertInfAndNaN); + } + + return octave_value (json.GetString ()); + +#else + + octave_unused_parameter (args); + + err_disabled_feature ("jsonencode", + "RapidJSON is required for JSON encoding\\decoding"); + +#endif +} diff -r 652a675c0916 -r 5da49e37a6c9 libinterp/corefcn/module.mk --- a/libinterp/corefcn/module.mk Mon Aug 03 11:13:26 2020 -0400 +++ b/libinterp/corefcn/module.mk Thu Aug 13 23:57:07 2020 +0900 @@ -185,6 +185,8 @@ %reldir%/interpreter-private.cc \ %reldir%/interpreter.cc \ %reldir%/inv.cc \ + %reldir%/jsondecode.cc \ + %reldir%/jsonencode.cc \ %reldir%/kron.cc \ %reldir%/load-path.cc \ %reldir%/load-save.cc \ diff -r 652a675c0916 -r 5da49e37a6c9 scripts/help/__unimplemented__.m --- a/scripts/help/__unimplemented__.m Mon Aug 03 11:13:26 2020 -0400 +++ b/scripts/help/__unimplemented__.m Thu Aug 13 23:57:07 2020 +0900 @@ -958,8 +958,6 @@ "javaMethodEDT", "javaObjectEDT", "join", - "jsondecode", - "jsonencode", "juliandate", "labeledge", "labelnode", diff -r 652a675c0916 -r 5da49e37a6c9 test/json/jsondecodetest.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/json/jsondecodetest.tst Thu Aug 13 23:57:07 2020 +0900 @@ -0,0 +1,507 @@ +% test jsondecode + +% Note: This script is intended to be a script-based unit test +% for MATLAB to test compatibility. Don't break that! + +%% Test 1: decode null values + +% null, in nonnumeric arrays to Empty double [] +%!testif HAVE_RAPIDJSON +%! json = '["str", 5, null, true]'; +%! exp = {'str'; 5; []; true}; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% null, in numeric arrays to NaN +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '[1, 2, null, 3]'; +%! exp = [1; 2; NaN; 3]; +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +% corner case: array of null values +%!testif HAVE_RAPIDJSON +%! json = '[null, null, null]'; +%! exp = [NaN; NaN; NaN]; +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +%% Test 2: decode Boolean, Number and String values + +%!testif HAVE_RAPIDJSON +%! assert (jsondecode ('true')); +%! assert (~ jsondecode ('false')); +%! assert (isequal (123.45, jsondecode ('123.45'))); +%! assert (isequal ('hello there', jsondecode ('"hello there"'))); + +%% Test 3: decode Array of Booleans, Numbers and Strings values +%!testif HAVE_RAPIDJSON +%! json = '[true, true, false, true]'; +%! exp = [1; 1; 0; 1]; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! json = '["true", "true", "false", "true"]'; +%! exp = {'true'; 'true'; 'false'; 'true'}; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! json = '["foo", "bar", ["foo", "bar"]]'; +%! exp = {'foo'; 'bar'; {'foo'; 'bar'}}; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! json = '[15000, 5, 12.25, 1502302.3012]'; +%! exp = [15000; 5; 12.25; 1502302.3012]; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '[[1,2]]'; +%! exp = [1 2]; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% If they have the same dimensions -> transform to an array +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '[[1, 2], [3, 4]]'; +%! exp = [1 2; 3 4]; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]'; +%! exp = cat (3, [1, 3; 5, 7], [2, 4; 6, 8]); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% try different dimensions for the array +%!testif HAVE_RAPIDJSON +%! json = '[[[1, 2, -1], [3, 4, null]], [[5, 6, Inf], [7, 8, -Inf]]]'; +%! exp = cat (3, [1, 3; 5, 7], [2, 4; 6, 8], [-1, NaN; Inf, -Inf]); +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +% try different dimensions for the array +%!testif HAVE_RAPIDJSON +%! json = '[[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]'; +%! exp = cat (3, [1, 3; 5, 7; 9, 11], [2, 4; 6, 8; 10, 12]); +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +% try higher dimensions for the array +%!testif HAVE_RAPIDJSON +%! json = ['[[[[1,-1], [2,-2]],[[3,-3],[4,-4]]],[[[5,-5],[6,-6]],[[7,-7],', ... +%! '[8,-8]]],[[[9,-9], [10,-10]],[[11,-11],[12,-12]]],', ... +%! '[[[13,-13],[14,-14]],[[15,-15],[16,-16]]]]']; +%! tmp1 = cat (3, [1, 3; 5, 7; 9, 11; 13, 15], [2, 4; 6, 8; 10, 12; 14, 16]); +%! tmp2 = cat (3, [-1, -3; -5, -7; -9, -11; -13, -15], ... +%! [-2, -4; -6, -8; -10, -12; -14, -16]); +%! exp = cat (4, tmp1, tmp2); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! json = '[[true, false], [true, false], [true, false]]'; +%! exp = [1 0; 1 0; 1 0]; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% If they have different dimensions -> transform to a cell array +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '[[1, 2], [3, 4, 5]]'; +%! exp = {[1; 2]; [3; 4; 5]}; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '[1, 2, [3, 4]]'; +%! exp = {1; 2; [3; 4]}; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! json = '[true, false, [true, false, false]]'; +%! exp = {1; 0; [1; 0; 0]}; +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%% Test 4: decode JSON objects + +% check the decoding of Boolean, Number and String values inside an object +%!testif HAVE_RAPIDJSON +%! json = '{"number": 3.14, "string": "string", "boolean": false}'; +%! exp = struct ('number', 3.14, 'string', 'string', 'boolean', 0); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% check the decoding of null values and arrays inside an object & makeValidName +%!testif HAVE_RAPIDJSON +%! json = '{"nonnumeric array": ["str", 5, null], "numeric array": [1, 2, null]}'; +%! exp = struct ('nonnumericArray',{{'str'; 5; []}}, 'numericArray', {[1; 2; NaN]}); +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +% check the decoding of objects inside an object & makeValidName +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '{"object": {" field 1 ": 1, "field- 2": 2, "3field": 3, "": 1}}'; +%! exp = struct ('object', struct ('field1', 1, 'field_2', 2, 'x3field', 3, 'x', 1)); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% check the decoding of empty objects, empty arrays and Inf inside an object +%!testif HAVE_RAPIDJSON +%! json = '{"a": Inf, "b": [], "c": {}}'; +%! exp = struct ('a', Inf, 'b', [], 'c', struct ()); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% check the decoding of string arrays inside an object & makeValidName +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '{"%string.array": ["Statistical","Parametric","Mapping"]}'; +%! exp = struct ('x_string_array', {{'Statistical'; 'Parametric'; 'Mapping'}}); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% a big test +% extracted from jsonlab +%!testif HAVE_RAPIDJSON +%! json = ['{' , ... +%! '"glossary": { ', ... +%! '"title": "example glossary",', ... +%! '"GlossDiv": {', ... +%! '"title": "S",', ... +%! '"GlossList": {', ... +%! '"GlossEntry": {', ... +%! '"ID": "SGML",', ... +%! '"SortAs": "SGML",', ... +%! '"GlossTerm": "Standard Generalized Markup Language",', ... +%! '"Acronym": "SGML",', ... +%! '"Abbrev": "ISO 8879:1986",', ... +%! '"GlossDef": {', ... +%! '"para": "A meta-markup language, ', ... +%! 'used to create markup languages such as DocBook.",', ... +%! '"GlossSeeAlso": ["GML", "XML"]', ... +%! '},', ... +%! '"GlossSee": "markup"', ... +%! '}', ... +%! '}', ... +%! '}', ... +%! '}', ... +%! '}']; +%! tmp1 = struct ('para', ['A meta-markup language, used to create markup languages', ... +%! ' such as DocBook.'],'GlossSeeAlso', {{'GML'; 'XML'}}); +%! tmp2 = struct ('ID', 'SGML', 'SortAs', 'SGML', 'GlossTerm', ... +%! 'Standard Generalized Markup Language', 'Acronym', 'SGML', ... +%! 'Abbrev', 'ISO 8879:1986', 'GlossDef', tmp1, 'GlossSee', 'markup'); +%! exp = struct ('glossary', struct ('title', 'example glossary', 'GlossDiv', ... +%! struct ('title', 'S', 'GlossList', struct ('GlossEntry', tmp2)))); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +%% Test 5: decode Array of JSON objects + +% arrays with the same field names in the same order +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '{"structarray": [{"a":1,"b":2},{"a":3,"b":4}]}'; +%! exp = struct ('structarray', struct ('a', {1; 3}, 'b', {2; 4})); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% different field names before calling makeValidName but the same after calling it +%! json = [ '[', ... +%! '{', ... +%! '"i*d": 0,', ... +%! '"12name": "Osborn"', ... +%! '},', ... +%! '{', ... +%! '"i/d": 1,', ... +%! '"12name": "Mcdowell"', ... +%! '},', ... +%! '{', ... +%! '"i+d": 2,', ... +%! '"12name": "Jewel"', ... +%! '}', ... +%! ']']; +%! exp = struct ('i_d', {0; 1; 2}, 'x12name', {'Osborn'; 'Mcdowell'; 'Jewel'}); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% arrays with the same field names in the same order and a big test +% json text is generated from json-generator.com +%!testif HAVE_RAPIDJSON +%! json = ['[', ... +%! '{', ... +%! '"x_id": "5ee28980fc9ab3",', ... +%! '"index": 0,', ... +%! '"guid": "b229d1de-f94a",', ... +%! '"latitude": -17.124067,', ... +%! '"longitude": -61.161831,', ... +%! '"friends": [', ... +%! '{', ... +%! '"id": 0,', ... +%! '"name": "Collins"', ... +%! '},', ... +%! '{', ... +%! '"id": 1,', ... +%! '"name": "Hays"', ... +%! '},', ... +%! '{', ... +%! '"id": 2,', ... +%! '"name": "Griffin"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{', ... +%! '"x_id": "5ee28980dd7250",', ... +%! '"index": 1,', ... +%! '"guid": "39cee338-01fb",', ... +%! '"latitude": 13.205994,', ... +%! '"longitude": -37.276231,', ... +%! '"friends": [', ... +%! '{', ... +%! '"id": 0,', ... +%! '"name": "Osborn"', ... +%! '},', ... +%! '{', ... +%! '"id": 1,', ... +%! '"name": "Mcdowell"', ... +%! '},', ... +%! '{', ... +%! '"id": 2,', ... +%! '"name": "Jewel"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{', ... +%! '"x_id": "5ee289802422ac",', ... +%! '"index": 2,', ... +%! '"guid": "3db8d55a-663e",', ... +%! '"latitude": -35.453456,', ... +%! '"longitude": 14.080287,', ... +%! '"friends": [', ... +%! '{', ... +%! '"id": 0,', ... +%! '"name": "Socorro"', ... +%! '},', ... +%! '{', ... +%! '"id": 1,', ... +%! '"name": "Darla"', ... +%! '},', ... +%! '{', ... +%! '"id": 2,', ... +%! '"name": "Leanne"', ... +%! '}', ... +%! ']', ... +%! '}', ... +%! ']']; +%! tmp1 = struct ('id', {0; 1; 2}, 'name', {'Collins'; 'Hays'; 'Griffin'}); +%! tmp2 = struct ('id', {0; 1; 2}, 'name', {'Osborn'; 'Mcdowell'; 'Jewel'}); +%! tmp3 = struct ('id', {0; 1; 2}, 'name', {'Socorro'; 'Darla'; 'Leanne'}); +%! exp = struct ('x_id', {'5ee28980fc9ab3'; '5ee28980dd7250'; '5ee289802422ac'}, ... +%! 'index', {0; 1; 2}, 'guid', {'b229d1de-f94a'; '39cee338-01fb'; '3db8d55a-663e'}, ... +%! 'latitude', {-17.124067; 13.205994; -35.453456}, 'longitude', ... +%! {-61.161831; -37.276231; 14.080287}, 'friends', {tmp1; tmp2; tmp3}); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% arrays with the same field names in different order +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '{"cellarray": [{"a":1,"b":2},{"b":3,"a":4}]}'; +%! exp = struct ('cellarray', {{struct('a', 1, 'b', 2); struct('b', 3, 'a', 4)}}); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% arrays with different field names +% extracted from JSONio +%!testif HAVE_RAPIDJSON +%! json = '{"cellarray": [{"a":1,"b":2},{"a":3,"c":4}]}'; +%! exp = struct ('cellarray', {{struct('a', 1, 'b', 2); struct('a', 3, 'c', 4)}}); +%! act = jsondecode (json); +%! assert (isequal (exp, act)); + +% arrays with different field names and a big test +%!testif HAVE_RAPIDJSON +%! json = ['[', ... +%! '{', ... +%! '"x_id": "5ee28980fc9ab3",', ... +%! '"index": 0,', ... +%! '"guid": "b229d1de-f94a",', ... +%! '"latitude": -17.124067,', ... +%! '"longitude": -61.161831,', ... +%! '"friends": [', ... +%! '{', ... +%! '"id": 0,', ... +%! '"name": "Collins"', ... +%! '},', ... +%! '{', ... +%! '"id": 1,', ... +%! '"name": "Hays"', ... +%! '},', ... +%! '{', ... +%! '"id": 2,', ... +%! '"name": "Griffin"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{"numeric array": ["str", 5, null], "nonnumeric array": [1, 2, null]},', ... +%! '{', ... +%! '"firstName": "John",', ... +%! '"lastName": "Smith",', ... +%! '"age": 25,', ... +%! '"address":', ... +%! '{', ... +%! '"streetAddress": "21 2nd Street",', ... +%! '"city": "New York",', ... +%! '"state": "NY"', ... +%! '},', ... +%! '"phoneNumber":', ... +%! '{', ... +%! '"type": "home",', ... +%! '"number": "212 555-1234"', ... +%! '}', ... +%! '}]']; +%! tmp1 = struct ('x_id', '5ee28980fc9ab3', 'index', 0, 'guid', 'b229d1de-f94a', ... +%! 'latitude', -17.124067, 'longitude', -61.161831, 'friends', ... +%! struct ('id', {0; 1; 2}, 'name', {'Collins'; 'Hays'; 'Griffin'})); +%! tmp2 = struct ('numericArray',{{'str'; 5; []}}, 'nonnumericArray', {[1; 2; NaN]}); +%! tmp3 = struct ('firstName', 'John','lastName', 'Smith', 'age', 25, 'address', ... +%! struct('streetAddress', '21 2nd Street', 'city', 'New York', 'state', 'NY'), ... +%! 'phoneNumber', struct ('type', 'home', 'number', '212 555-1234')); +%! exp = {tmp1; tmp2; tmp3}; +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +%% Test 6: decode Array of different JSON data types +%!testif HAVE_RAPIDJSON +%! json = ['[null, true, Inf, 2531.023, "hello there", ', ... +%! '{', ... +%! '"x_id": "5ee28980dd7250",', ... +%! '"index": 1,', ... +%! '"guid": "39cee338-01fb",', ... +%! '"latitude": 13.205994,', ... +%! '"longitude": -37.276231,', ... +%! '"friends": [', ... +%! '{', ... +%! '"id": 0,', ... +%! '"name": "Osborn"', ... +%! '},', ... +%! '{', ... +%! '"id": 1,', ... +%! '"name": "Mcdowell"', ... +%! '},', ... +%! '{', ... +%! '"id": 2,', ... +%! '"name": "Jewel"', ... +%! '}', ... +%! ']', ... +%! '}]']; +%! tmp = struct ('x_id', '5ee28980dd7250', 'index', 1, 'guid', '39cee338-01fb', ... +%! 'latitude', 13.205994, 'longitude', -37.276231, 'friends', ... +%! struct ('id', {0; 1; 2}, 'name', {'Osborn'; 'Mcdowell'; 'Jewel'})); +%! exp = {[]; 1; Inf; 2531.023; 'hello there'; tmp}; +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +% Array of arrays +%!testif HAVE_RAPIDJSON +%! json = ['[["str", Inf, null], [1, 2, null], ["foo", "bar", ["foo", "bar"]],', ... +%! '[[[1, 2], [3, 4]], [[5, 6], [7, 8]]],' , ... +%! '[', ... +%! '{', ... +%! '"x_id": "5ee28980fc9ab3",', ... +%! '"index": 0,', ... +%! '"guid": "b229d1de-f94a",', ... +%! '"latitude": -17.124067,', ... +%! '"longitude": -61.161831,', ... +%! '"friends": [', ... +%! '{', ... +%! '"id": 0,', ... +%! '"name": "Collins"', ... +%! '},', ... +%! '{', ... +%! '"id": 1,', ... +%! '"name": "Hays"', ... +%! '},', ... +%! '{', ... +%! '"id": 2,', ... +%! '"name": "Griffin"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{"numeric array": ["str", 5, null], "nonnumeric array": [1, 2, null]},', ... +%! '{', ... +%! '"firstName": "John",', ... +%! '"lastName": "Smith",', ... +%! '"age": 25,', ... +%! '"address":', ... +%! '{', ... +%! '"streetAddress": "21 2nd Street",', ... +%! '"city": "New York",', ... +%! '"state": "NY"', ... +%! '},', ... +%! '"phoneNumber":', ... +%! '{', ... +%! '"type": "home",', ... +%! '"number": "212 555-1234"', ... +%! '}', ... +%! '}]]']; +%! tmp1 = struct ('x_id', '5ee28980fc9ab3', 'index', 0, 'guid', 'b229d1de-f94a', ... +%! 'latitude', -17.124067, 'longitude', -61.161831, 'friends', ... +%! struct ('id', {0; 1; 2}, 'name', {'Collins'; 'Hays'; 'Griffin'})); +%! tmp2 = struct ('numericArray',{{'str'; 5; []}}, 'nonnumericArray', {[1; 2; NaN]}); +%! tmp3 = struct ('firstName', 'John','lastName', 'Smith', 'age', 25, 'address', ... +%! struct('streetAddress', '21 2nd Street', 'city', 'New York', 'state', 'NY'), ... +%! 'phoneNumber', struct ('type', 'home', 'number', '212 555-1234')); +%! exp = {{'str'; Inf; []}; [1; 2; NaN]; {'foo'; 'bar'; {'foo'; 'bar'}}; +%! cat(3, [1, 3; 5, 7], [2, 4; 6, 8]); {tmp1; tmp2 ;tmp3}}; +%! act = jsondecode (json); +%! assert (isequaln (exp, act)); + +%% Test 7: Forward "ReplacementStyle" and "Prefix" options + +%!testif HAVE_RAPIDJSON +%! json = '{"1a": {"1*a": {"1+*/-a": {"1#a": {}}}}}'; +%! exp = struct ('n1a', struct ('n1a', struct ('n1a', struct ('n1a', struct ())))); +%! act = jsondecode (json, "ReplacementStyle", "delete", "Prefix", "_", "Prefix", "n"); +%! assert (isequal (exp, act)); + +% Check the forwarding of "ReplacementStyle" and "Prefix" options inside arrays +%!testif HAVE_RAPIDJSON +%! json = [ '[', ... +%! '{', ... +%! '"i*d": 0,', ... +%! '"12name": "Osborn"', ... +%! '},', ... +%! '{', ... +%! '"i*d": 1,', ... +%! '"12name": "Mcdowell"', ... +%! '},', ... +%! '{', ... +%! '"i*d": 2,', ... +%! '"12name": "Jewel"', ... +%! '}', ... +%! ']']; +%! exp = struct ('i0x2Ad', {0; 1; 2}, 'm_12name', {'Osborn'; 'Mcdowell'; 'Jewel'}); +%! act = jsondecode (json, "ReplacementStyle", "hex", "Prefix", "m_"); +%! assert (isequal (exp, act)); + +% Check the forwarding of "ReplacementStyle" and "Prefix" options inside arrays +%!testif HAVE_RAPIDJSON +%! json = '{"cell*array": [{"1a":1,"b*1":2},{"1a":3,"b/2":4}]}'; +%! exp = struct ('cell_array', {{struct('x_1a', 1, 'b_1', 2); struct('x_1a', 3, 'b_2', 4)}}); +%! act = jsondecode (json, "ReplacementStyle", "underscore", "Prefix", "x_"); +%! assert (isequal (exp, act)); diff -r 652a675c0916 -r 5da49e37a6c9 test/json/jsonencodetest.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/json/jsonencodetest.tst Thu Aug 13 23:57:07 2020 +0900 @@ -0,0 +1,580 @@ +% test jsonencode + +% Note: This script is intended to be a script-based unit test +% for MATLAB to test compatibility. Don't break that! + +% some tests here are just the reverse of tests in jsondecode with some modifiactions +%% Test 1: encode logical and numeric scalars, NaN and Inf +%!testif HAVE_RAPIDJSON +%! assert (isequal (jsonencode (true), 'true')); +%! assert (isequal (jsonencode (50.025), '50.025')); +%! assert (isequal (jsonencode (NaN), 'null')); +%! assert (isequal (jsonencode (Inf), 'null')); +%! assert (isequal (jsonencode (-Inf), 'null')); + +% Customized encoding of Nan, Inf, -Inf +%!testif HAVE_RAPIDJSON +%! assert (isequal (jsonencode (NaN, 'ConvertInfAndNaN', true), 'null')); +%! assert (isequal (jsonencode (Inf, 'ConvertInfAndNaN', true), 'null')); +%! assert (isequal (jsonencode (-Inf, 'ConvertInfAndNaN', true), 'null')); + +%!testif HAVE_RAPIDJSON +%! assert (isequal (jsonencode (NaN, 'ConvertInfAndNaN', false), 'NaN')); +%! assert (isequal (jsonencode (Inf, 'ConvertInfAndNaN', false), 'Infinity')); +%! assert (isequal (jsonencode (-Inf, 'ConvertInfAndNaN', false), '-Infinity')); + +%% Test 2: encode character vectors and arrays +%!testif HAVE_RAPIDJSON +%! assert (isequal (jsonencode (''), '""')); +%! assert (isequal (jsonencode ('hello there'), '"hello there"')); +%! assert (isequal (jsonencode (['foo'; 'bar']), '["foo","bar"]')); +%! assert (isequal (jsonencode (['foo', 'bar'; 'foo', 'bar']), '["foobar","foobar"]')); + +%!testif HAVE_RAPIDJSON +%! data = [[['foo'; 'bar']; ['foo'; 'bar']], [['foo'; 'bar']; ['foo'; 'bar']]]; +%! exp = '["foofoo","barbar","foofoo","barbar"]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! data = cat (3, ['a', 'b'; 'c', 'd'], ['e', 'f'; 'g', 'h']); +%! exp = '[["ab","ef"],["cd","gh"]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try different dimensions for the array +%!testif HAVE_RAPIDJSON +%! data = cat (3, ['a', 'b'; 'c', 'd'; '1', '2'], ['e', 'f'; 'g', 'h'; '3', '4']); +%! exp = '[["ab","ef"],["cd","gh"],["12","34"]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array +%!testif HAVE_RAPIDJSON +%! tmp1 = cat (3, ['1', '3'; '5', '7'; '9', 'e'; 'f', 'g'], ... +%! ['2', '4'; '6', '8'; 'a', 'b'; 'c', 'd']); +%! tmp2 = cat (3, ['1', '3'; '5', '7'; '9', 'e'; 'f', 'g'], ... +%! ['2', '4'; '6', '8'; 'a', 'b'; 'c', 'd']); +%! data = cat (4, tmp1, tmp2); +%! exp = ['[[["13","13"],["24","24"]],[["57","57"],["68","68"]],', ... +%! '[["9e","9e"],["ab","ab"]],[["fg","fg"],["cd","cd"]]]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try different dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = cat (4, ['a'; 'b'], ['c'; 'd']); +%! exp = '[[["a","c"]],[["b","d"]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try different dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = cat (8, ['a'], ['c']); +%! exp = '"ac"'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try different dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = cat (8, ['a'; 'b'; '1'], ['c'; 'd'; '2']); +%! exp = '[[[[[[["a","c"]]]]]],[[[[[["b","d"]]]]]],[[[[[["1","2"]]]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%% Test 3: encode numeric and logical arrays (with NaN and Inf) +% test simple vectors +%!testif HAVE_RAPIDJSON +%! assert (isequal (jsonencode ([]), '[]')); +%! assert (isequal (jsonencode ([1, 2, 3, 4]), '[1,2,3,4]')); +%! assert (isequal (jsonencode ([true; false; true]), '[true,false,true]')); + +% test arrays +%!testif HAVE_RAPIDJSON +%! data = [1 NaN; 3 4]; +%! exp = '[[1,null],[3,4]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! data = cat (3, [NaN, 3; 5, Inf], [2, 4; -Inf, 8]); +%! exp = '[[[null,2],[3,4]],[[5,null],[null,8]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% Customized encoding of Nan, Inf, -Inf +%!testif HAVE_RAPIDJSON +%! data = cat (3, [1, NaN; 5, 7], [2, Inf; 6, -Inf]); +%! exp = '[[[1,2],[NaN,Infinity]],[[5,6],[7,-Infinity]]]'; +%! act = jsonencode (data, 'ConvertInfAndNaN', false); +%! assert (isequal (exp, act)); + +% try different dimensions for the array +%!testif HAVE_RAPIDJSON +%! data = cat (3, [1, 3; 5, 7], [2, 4; 6, 8], [-1, NaN; Inf, -Inf]); +%! exp = '[[[1,2,-1],[3,4,NaN]],[[5,6,Infinity],[7,8,-Infinity]]]'; +%! act = jsonencode (data, 'ConvertInfAndNaN', false); +%! assert (isequal (exp, act)); + +% try different dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = cat (3, [1; 7; 11], [4; 8; 12]); +%! exp = '[[[1,4]],[[7,8]],[[11,12]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! tmp1 = cat (3, [5, 7], [2, 4]); +%! tmp2 = cat (3, [-1, -3], [-2, -4]); +%! data = cat (4, tmp1, tmp2); +%! exp = '[[[[5,-1],[2,-2]],[[7,-3],[4,-4]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! tmp1 = cat (3, [5; 7], [2; 4]); +%! tmp2 = cat (3, [-1; -3], [-2; -4]); +%! data = cat (4, tmp1, tmp2); +%! exp = '[[[[5,-1],[2,-2]]],[[[7,-3],[4,-4]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = cat (4, [1, 3; 5, 7], [-1, -3; -5, -7]); +%! exp = '[[[[1,-1]],[[3,-3]]],[[[5,-5]],[[7,-7]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 1 1 1 1 6]); +%! exp = '[1,1,1,1,1,1]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with one of its dimensions equals one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 2 2 2 2]); +%! exp = '[[[[[1,1],[1,1]],[[1,1],[1,1]]],[[[1,1],[1,1]],[[1,1],[1,1]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 2 2 1 2]); +%! exp = '[[[[[1,1]],[[1,1]]],[[[1,1]],[[1,1]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 2 1 2 1 2]); +%! exp = '[[[[[[1,1]],[[1,1]]]],[[[[1,1]],[[1,1]]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 1 2 1 2 1 2]); +%! exp = '[[[[[[[1,1]],[[1,1]]]],[[[[1,1]],[[1,1]]]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 2 2 1 1 2]); +%! exp = '[[[[[[1,1]]],[[[1,1]]]],[[[[1,1]]],[[[1,1]]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 2 1 3 1 1 1 2]); +%! exp = ['[[[[[[[[1,1]]]],[[[[1,1]]]],[[[[1,1]]]]]],[[[[[[1,1]]]],', ... +%! '[[[[1,1]]]],[[[[1,1]]]]]]]]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 1 1 1 2 1 1 1 2]); +%! exp = '[[[[[[[[[1,1]]]],[[[[1,1]]]]]]]]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 3 2 1 1 2 1 2 2]); +%! exp = ['[[[[[[[[[1,1],[1,1]]],[[[1,1],[1,1]]]]]],[[[[[[1,1],', ... +%! '[1,1]]],[[[1,1],[1,1]]]]]]],[[[[[[[1,1],[1,1]]],[[[1,', ... +%! '1],[1,1]]]]]],[[[[[[1,1],[1,1]]],[[[1,1],[1,1]]]]]]],', ... +%! '[[[[[[[1,1],[1,1]]],[[[1,1],[1,1]]]]]],[[[[[[1,1],[1,', ... +%! '1]]],[[[1,1],[1,1]]]]]]]]]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array with some of its dimensions equal one +%!testif HAVE_RAPIDJSON +%! data = ones ([1 1 1 1 1 1 2 1 1 2 2 3 1 1 1 1 1 1 1 2]); +%! exp = ['[[[[[[[[[[[[[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]],', ... +%! '[[[[[[[[1,1]]]]]]]]],[[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]', ... +%! ']]],[[[[[[[[1,1]]]]]]]]]],[[[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]', ... +%! ']]]]]]],[[[[[[[[1,1]]]]]]]]],[[[[[[[[[1,1]]]]]]]],[[[[[[', ... +%! '[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]]]]]]],[[[[[[[[[[[[[1,1]', ... +%! ']]]]]]],[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]]],[[[[[[[[', ... +%! '[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]]]],[[[', ... +%! '[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]]],', ... +%! '[[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]]]]]]],[[[[[[[[1,1]]', ... +%! ']]]]]]]]]]]]]]]]]]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% try higher dimensions for the array +%!testif HAVE_RAPIDJSON +%! tmp1 = cat (3, [1, 3; 5, 7; 9, 11; 13, 15], [2, 4; 6, 8; 10, 12; 14, 16]); +%! tmp2 = cat (3, [-1, -3; -5, -7; -9, -11; -13, -15], ... +%! [-2, -4; -6, -8; -10, -12; -14, -16]); +%! data = cat (4, tmp1, tmp2); +%! exp = ['[[[[1,-1],[2,-2]],[[3,-3],[4,-4]]],[[[5,-5],[6,-6]],[[7,-7],', ... +%! '[8,-8]]],[[[9,-9],[10,-10]],[[11,-11],[12,-12]]],', ... +%! '[[[13,-13],[14,-14]],[[15,-15],[16,-16]]]]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! data = [true false; true false; true false]; +%! exp = '[[true,false],[true,false],[true,false]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%% Test 4: encode containers.Map + +% KeyType must be char to encode objects of containers.Map +%!testif HAVE_RAPIDJSON +%! assert (isequal (jsonencode (containers.Map('1', [1, 2, 3])), '{"1":[1,2,3]}')); + +%!testif HAVE_RAPIDJSON +%! data = containers.Map({'foo'; 'bar'; 'baz'}, [1, 2, 3]); +%! exp = '{"bar":2,"baz":3,"foo":1}'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! data = containers.Map({'foo'; 'bar'; 'baz'}, {{1, 'hello', NaN}, true, [2, 3, 4]}); +%! exp = '{"bar":true,"baz":[2,3,4],"foo":[1,"hello",NaN]}'; +%! act = jsonencode (data, 'ConvertInfAndNaN', false); +%! assert (isequal (exp, act)); + +%% Test 5: encode scalar structs +% check the encoding of Boolean, Number and String values inside a struct +%!testif HAVE_RAPIDJSON +%! data = struct ('number', 3.14, 'string', 'string', 'boolean', false); +%! exp = '{"number":3.14,"string":"string","boolean":false}'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% check the decoding of null, Inf and -Inf values inside a struct +%!testif HAVE_RAPIDJSON +%! data = struct ('numericArray', [7, NaN, Inf, -Inf]); +%! exp = '{"numericArray":[7,null,null,null]}'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% Customized encoding of Nan, Inf, -Inf +%!testif HAVE_RAPIDJSON +%! data = struct ('numericArray', [7, NaN, Inf, -Inf]); +%! exp = '{"numericArray":[7,NaN,Infinity,-Infinity]}'; +%! act = jsonencode (data, 'ConvertInfAndNaN', false); +%! assert (isequal (exp, act)); + +% check the encoding of structs inside a struct +%!testif HAVE_RAPIDJSON +%! data = struct ('object', struct ('field1', 1, 'field2', 2, 'field3', 3)); +%! exp = '{"object":{"field1":1,"field2":2,"field3":3}}'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% check the encoding of empty structs, empty arrays and Inf inside a struct +%!testif HAVE_RAPIDJSON +%! data = struct ('a', Inf, 'b', [], 'c', struct ()); +%! exp = '{"a":null,"b":[],"c":{}}'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% a big test +%!testif HAVE_RAPIDJSON +%! tmp1 = struct ('para', ['A meta-markup language, used to create markup languages', ... +%! ' such as DocBook.'],'GlossSeeAlso', {{'GML'; 'XML'}}); +%! tmp2 = struct ('ID', 'SGML', 'SortAs', 'SGML', 'GlossTerm', ... +%! 'Standard Generalized Markup Language', 'Acronym', 'SGML', ... +%! 'Abbrev', 'ISO 8879:1986', 'GlossDef', tmp1, 'GlossSee', 'markup'); +%! data = struct ('glossary', struct ('title', 'example glossary', 'GlossDiv', ... +%! struct ('title', 'S', 'GlossList', struct ('GlossEntry', tmp2)))); +%! exp = ['{' , ... +%! '"glossary":{', ... +%! '"title":"example glossary",', ... +%! '"GlossDiv":{', ... +%! '"title":"S",', ... +%! '"GlossList":{', ... +%! '"GlossEntry":{', ... +%! '"ID":"SGML",', ... +%! '"SortAs":"SGML",', ... +%! '"GlossTerm":"Standard Generalized Markup Language",', ... +%! '"Acronym":"SGML",', ... +%! '"Abbrev":"ISO 8879:1986",', ... +%! '"GlossDef":{', ... +%! '"para":"A meta-markup language, ', ... +%! 'used to create markup languages such as DocBook.",', ... +%! '"GlossSeeAlso":["GML","XML"]', ... +%! '},', ... +%! '"GlossSee":"markup"', ... +%! '}', ... +%! '}', ... +%! '}', ... +%! '}', ... +%! '}']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%% Test 6: encode struct arrays +%!testif HAVE_RAPIDJSON +%! data = struct ('structarray', struct ('a', {1; 3}, 'b', {2; 4})); +%! exp = '{"structarray":[{"a":1,"b":2},{"a":3,"b":4}]}'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% a big Test +%!testif HAVE_RAPIDJSON +%! tmp1 = struct ('id', {0; 1; 2}, 'name', {'Collins'; 'Hays'; 'Griffin'}); +%! tmp2 = struct ('id', {0; 1; 2}, 'name', {'Osborn'; 'Mcdowell'; 'Jewel'}); +%! tmp3 = struct ('id', {0; 1; 2}, 'name', {'Socorro'; 'Darla'; 'Leanne'}); +%! data = struct ('x_id', {'5ee28980fc9ab3'; '5ee28980dd7250'; '5ee289802422ac'}, ... +%! 'index', {0; 1; 2}, 'guid', {'b229d1de-f94a'; '39cee338-01fb'; '3db8d55a-663e'}, ... +%! 'latitude', {-17.124067; 13.205994; -35.453456}, 'longitude', ... +%! {-61.161831; -37.276231; 14.080287}, 'friends', {tmp1; tmp2; tmp3}); +%! exp = ['[', ... +%! '{', ... +%! '"x_id":"5ee28980fc9ab3",', ... +%! '"index":0,', ... +%! '"guid":"b229d1de-f94a",', ... +%! '"latitude":-17.124067,', ... +%! '"longitude":-61.161831,', ... +%! '"friends":[', ... +%! '{', ... +%! '"id":0,', ... +%! '"name":"Collins"', ... +%! '},', ... +%! '{', ... +%! '"id":1,', ... +%! '"name":"Hays"', ... +%! '},', ... +%! '{', ... +%! '"id":2,', ... +%! '"name":"Griffin"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{', ... +%! '"x_id":"5ee28980dd7250",', ... +%! '"index":1,', ... +%! '"guid":"39cee338-01fb",', ... +%! '"latitude":13.205994,', ... +%! '"longitude":-37.276231,', ... +%! '"friends":[', ... +%! '{', ... +%! '"id":0,', ... +%! '"name":"Osborn"', ... +%! '},', ... +%! '{', ... +%! '"id":1,', ... +%! '"name":"Mcdowell"', ... +%! '},', ... +%! '{', ... +%! '"id":2,', ... +%! '"name":"Jewel"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{', ... +%! '"x_id":"5ee289802422ac",', ... +%! '"index":2,', ... +%! '"guid":"3db8d55a-663e",', ... +%! '"latitude":-35.453456,', ... +%! '"longitude":14.080287,', ... +%! '"friends":[', ... +%! '{', ... +%! '"id":0,', ... +%! '"name":"Socorro"', ... +%! '},', ... +%! '{', ... +%! '"id":1,', ... +%! '"name":"Darla"', ... +%! '},', ... +%! '{', ... +%! '"id":2,', ... +%! '"name":"Leanne"', ... +%! '}', ... +%! ']', ... +%! '}', ... +%! ']']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%% Test 7: encode cell arrays +%!testif HAVE_RAPIDJSON +%! assert (isequal ('[]', jsonencode ({}))); +%! assert (isequal ('[5]', jsonencode ({5}))); +%! assert (isequal ('["hello there"]', jsonencode ({'hello there'}))); + +%!testif HAVE_RAPIDJSON +%! data = {'true', 'true'; 'false', 'true'}; +%! exp = '["true","false","true","true"]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +%!testif HAVE_RAPIDJSON +%! data = {'foo'; 'bar'; {'foo'; 'bar'}}; +%! exp = '["foo","bar",["foo","bar"]]'; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% cell array of structs & a big test +%!testif HAVE_RAPIDJSON +%! tmp1 = struct ('x_id', '5ee28980fc9ab3', 'index', 0, 'guid', 'b229d1de-f94a', ... +%! 'latitude', -17.124067, 'longitude', -61.161831, 'friends', ... +%! struct ('id', {0; 1; 2}, 'name', {'Collins'; 'Hays'; 'Griffin'})); +%! tmp2 = struct ('numericArray',{{'str'; 5; []}}, 'nonnumericArray', {[1; 2; NaN]}); +%! tmp3 = struct ('firstName', 'John','lastName', 'Smith', 'age', 25, 'address', ... +%! struct('streetAddress', '21 2nd Street', 'city', 'New York', 'state', 'NY'), ... +%! 'phoneNumber', struct ('type', 'home', 'number', '212 555-1234')); +%! data = {tmp1; tmp2; tmp3}; +%! exp = ['[', ... +%! '{', ... +%! '"x_id":"5ee28980fc9ab3",', ... +%! '"index":0,', ... +%! '"guid":"b229d1de-f94a",', ... +%! '"latitude":-17.124067,', ... +%! '"longitude":-61.161831,', ... +%! '"friends":[', ... +%! '{', ... +%! '"id":0,', ... +%! '"name":"Collins"', ... +%! '},', ... +%! '{', ... +%! '"id":1,', ... +%! '"name":"Hays"', ... +%! '},', ... +%! '{', ... +%! '"id":2,', ... +%! '"name":"Griffin"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{"numericArray":["str",5,[]],"nonnumericArray":[1,2,null]},', ... +%! '{', ... +%! '"firstName":"John",', ... +%! '"lastName":"Smith",', ... +%! '"age":25,', ... +%! '"address":', ... +%! '{', ... +%! '"streetAddress":"21 2nd Street",', ... +%! '"city":"New York",', ... +%! '"state":"NY"', ... +%! '},', ... +%! '"phoneNumber":', ... +%! '{', ... +%! '"type":"home",', ... +%! '"number":"212 555-1234"', ... +%! '}', ... +%! '}]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); + +% cell array of diferrent types & Customized encoding of Nan, Inf, -Inf +%!testif HAVE_RAPIDJSON +%! tmp = struct ('x_id', '5ee28980dd7250', 'index', 1, 'guid', '39cee338-01fb', ... +%! 'latitude', 13.205994, 'longitude', -37.276231, 'friends', ... +%! struct ('id', {0; 1; 2}, 'name', {'Osborn'; 'Mcdowell'; 'Jewel'})); +%! data = {NaN; true; Inf; 2531.023; 'hello there'; tmp}; +%! exp = ['[NaN,true,Infinity,2531.023,"hello there",', ... +%! '{', ... +%! '"x_id":"5ee28980dd7250",', ... +%! '"index":1,', ... +%! '"guid":"39cee338-01fb",', ... +%! '"latitude":13.205994,', ... +%! '"longitude":-37.276231,', ... +%! '"friends":[', ... +%! '{', ... +%! '"id":0,', ... +%! '"name":"Osborn"', ... +%! '},', ... +%! '{', ... +%! '"id":1,', ... +%! '"name":"Mcdowell"', ... +%! '},', ... +%! '{', ... +%! '"id":2,', ... +%! '"name":"Jewel"', ... +%! '}', ... +%! ']', ... +%! '}]']; +%! act = jsonencode (data, 'ConvertInfAndNaN', false); +%! assert (isequal (exp, act)); + +% a big example +%!testif HAVE_RAPIDJSON +%! tmp1 = struct ('x_id', '5ee28980fc9ab3', 'index', 0, 'guid', 'b229d1de-f94a', ... +%! 'latitude', -17.124067, 'longitude', -61.161831, 'friends', ... +%! struct ('id', {0; 1; 2}, 'name', {'Collins'; 'Hays'; 'Griffin'})); +%! tmp2 = struct ('numericArray',{{'str'; 5; -Inf}}, 'nonnumericArray', {[1; 2; NaN]}); +%! tmp3 = struct ('firstName', 'John','lastName', 'Smith', 'age', 25, 'address', ... +%! struct('streetAddress', '21 2nd Street', 'city', 'New York', 'state', 'NY'), ... +%! 'phoneNumber', struct ('type', 'home', 'number', '212 555-1234')); +%! data = {{'str'; Inf; {}}; [1; 2; NaN]; {'foo'; 'bar'; {'foo'; 'bar'}}; +%! cat(3, [1, 3; 5, 7], [2, 4; 6, 8]); {tmp1; tmp2 ;tmp3}}; +%! exp = ['[["str",null,[]],[1,2,null],["foo","bar",["foo","bar"]],', ... +%! '[[[1,2],[3,4]],[[5,6],[7,8]]],' , ... +%! '[', ... +%! '{', ... +%! '"x_id":"5ee28980fc9ab3",', ... +%! '"index":0,', ... +%! '"guid":"b229d1de-f94a",', ... +%! '"latitude":-17.124067,', ... +%! '"longitude":-61.161831,', ... +%! '"friends":[', ... +%! '{', ... +%! '"id":0,', ... +%! '"name":"Collins"', ... +%! '},', ... +%! '{', ... +%! '"id":1,', ... +%! '"name":"Hays"', ... +%! '},', ... +%! '{', ... +%! '"id":2,', ... +%! '"name":"Griffin"', ... +%! '}', ... +%! ']', ... +%! '},', ... +%! '{"numericArray":["str",5,null],"nonnumericArray":[1,2,null]},', ... +%! '{', ... +%! '"firstName":"John",', ... +%! '"lastName":"Smith",', ... +%! '"age":25,', ... +%! '"address":', ... +%! '{', ... +%! '"streetAddress":"21 2nd Street",', ... +%! '"city":"New York",', ... +%! '"state":"NY"', ... +%! '},', ... +%! '"phoneNumber":', ... +%! '{', ... +%! '"type":"home",', ... +%! '"number":"212 555-1234"', ... +%! '}', ... +%! '}]]']; +%! act = jsonencode (data); +%! assert (isequal (exp, act)); diff -r 652a675c0916 -r 5da49e37a6c9 test/json/module.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/json/module.mk Thu Aug 13 23:57:07 2020 +0900 @@ -0,0 +1,5 @@ +json_TEST_FILES = \ + %reldir%/jsondecodetest.tst \ + %reldir%/jsonencodetest.tst + +TEST_FILES += $(json_TEST_FILES) diff -r 652a675c0916 -r 5da49e37a6c9 test/module.mk --- a/test/module.mk Mon Aug 03 11:13:26 2020 -0400 +++ b/test/module.mk Thu Aug 13 23:57:07 2020 +0900 @@ -90,6 +90,7 @@ include %reldir%/colon-op/module.mk include %reldir%/ctor-vs-method/module.mk include %reldir%/fcn-handle/module.mk +include %reldir%/json/module.mk include %reldir%/local-functions/module.mk include %reldir%/mex/module.mk include %reldir%/nest/module.mk