Mercurial > octave
changeset 28750:80857685105b
Code review for jsonencode/jsondecode functions.
* jsondecode.cc, jsonencode.cc: Don't use period at end of error() text.
Don't explicitly call octave_value() on objects returned from functions declared
to return octave_value. Use true/false for bool values rather than 1/0.
Update comments and in-line documentation. Don't declare and initialize
multiple variabes on the same line. Use C++ R"()" syntax to avoid escaping
double quotes so frequently. Use Octave conventions for spacing between
literals and '('. Rewrite docstring and ues @multitable for clearer
presentation of conversions. Add BISTS tests for input validation.
* jsondecode.cc (Fjsondecode): Change reported offset error to use 1-based
indexing consistent with Octave arrays.
* jsonencode.cc (encode_numeric): Replace multiple tests for isnan, isinf, isna
with single call to isfinite.
* strings.txi: List jsonencode function before jsondecode. Change @menu
structure to reflect new ordering.
author | Rik <rik@octave.org> |
---|---|
date | Thu, 17 Sep 2020 05:07:02 -0700 |
parents | 4e10e25f0fc6 |
children | 699bba597610 |
files | doc/interpreter/strings.txi libinterp/corefcn/jsondecode.cc libinterp/corefcn/jsonencode.cc |
diffstat | 3 files changed, 215 insertions(+), 218 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/interpreter/strings.txi Wed Sep 16 13:15:35 2020 -0700 +++ b/doc/interpreter/strings.txi Thu Sep 17 05:07:02 2020 -0700 @@ -499,7 +499,7 @@ @menu * String encoding:: * Numerical Data and Strings:: -* JSON data decoding/encoding:: +* JSON data encoding/decoding:: @end menu @node String encoding @@ -549,16 +549,16 @@ @DOCSTRING(strread) -@node JSON data decoding/encoding -@subsection JSON data decoding/encoding +@node JSON data encoding/decoding +@subsection JSON data encoding/decoding JavaScript Object Notation, in short JSON, is a very common human readable -and structured data format. GNU Octave supports decoding and encoding this +and structured data format. GNU Octave supports encoding and decoding this format with the following two functions. -@DOCSTRING(jsondecode) +@DOCSTRING(jsonencode) -@DOCSTRING(jsonencode) +@DOCSTRING(jsondecode) @node Character Class Functions @section Character Class Functions
--- a/libinterp/corefcn/jsondecode.cc Wed Sep 16 13:15:35 2020 -0700 +++ b/libinterp/corefcn/jsondecode.cc Thu Sep 17 05:07:02 2020 -0700 @@ -71,7 +71,7 @@ else if (val.IsDouble ()) return octave_value (val.GetDouble ()); else - error ("jsondecode.cc: Unidentified type."); + error ("jsondecode: unidentified type"); } //! Decodes a JSON object into a scalar struct. @@ -93,15 +93,19 @@ decode_object (const rapidjson::Value& val, const octave_value_list& options) { octave_scalar_map retval; + + // Validator function to guarantee legitimate variable name. + std::string fcn_name = "matlab.lang.makeValidName"; + 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); + + return retval; } //! Decodes a JSON array that contains only numerical or null values @@ -189,7 +193,7 @@ return retval; } -//! Decodes a JSON array that contains only objects into a Cell or a struct array +//! Decodes a JSON array that contains only objects into a Cell or struct array //! depending on the similarity of the objects' keys. //! //! @param val JSON value that is guaranteed to be an object array. @@ -220,14 +224,16 @@ { 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 = true; for (octave_idx_type i = 1; i < struct_cell.numel (); ++i) if (field_names.std_list () != struct_cell(i).scalar_map_value ().fieldnames ().std_list ()) { - same_field_names = 0; + same_field_names = false; break; } + if (same_field_names) { octave_map struct_array; @@ -238,14 +244,14 @@ value(k) = struct_cell(k).scalar_map_value ().getfield (field_names(i)); struct_array.assign (field_names(i), value); } - return octave_value (struct_array); + return 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. +//! depending on the dimensions and element types 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. @@ -274,26 +280,28 @@ 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 + 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 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 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) + // 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); @@ -310,7 +318,7 @@ return array; } -//! Decodes any type of JSON arrays. This function only serves as an interface +//! 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. @@ -331,28 +339,31 @@ { // Handle empty arrays if (val.Empty ()) - return NDArray (dim_vector (0,0)); + return NDArray (); // 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; + // Check if the array is numeric and if it has multiple types + bool same_type = true; + bool is_numeric = true; 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; + is_numeric = false; 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; + same_type = false; } + if (is_numeric) return decode_numeric_array (val); + if (same_type && (array_type != rapidjson::kStringType)) { if (array_type == rapidjson::kTrueType @@ -363,13 +374,13 @@ else if (array_type == rapidjson::kArrayType) return decode_array_of_arrays (val, options); else - error ("jsondecode.cc: Unidentified type."); + error ("jsondecode: unidentified type"); } else return decode_string_and_mixed_array (val, options); } -//! Decodes any JSON value. This function only serves as an interface +//! 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. @@ -397,77 +408,52 @@ else if (val.IsObject ()) return decode_object (val, options); else if (val.IsNull ()) - return NDArray (dim_vector (0,0)); + return NDArray (); else if (val.IsArray ()) return decode_array (val, options); else - error ("jsondecode.cc: Unidentified type."); + error ("jsondecode: 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{}) +@deftypefn {} {@var{object} =} jsondecode (@var{JSON_txt}) +@deftypefnx {} {@var{object} =} jsondecode (@dots{}, "ReplacementStyle", @var{rs}) +@deftypefnx {} {@var{object} =} jsondecode (@dots{}, "Prefix", @var{pfx}) 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}. +The input @var{JSON_txt} is a string that contains JSON text. + +The output @var{object} is an Octave object that contains the result of +decoding @var{JSON_txt}. For more information about the options @qcode{"ReplacementStyle"} and @qcode{"Prefix"}, see @ref{XREFmatlab_lang_makeValidName,,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 -@code{matlab.lang.makeValidName}. +NOTE: Decoding and encoding JSON text is not guaranteed to reproduce the +original text as some names may be changed by @code{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{XREFmatlab_lang_makeValidName,,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 +@multitable @columnfractions 0.50 0.50 +@headitem JSON data type @tab Octave data type +@item Boolean @tab scalar logical +@item Number @tab scalar double +@item String @tab vector of characters +@item Object @tab scalar struct (field names of the struct may be different from the keys of the JSON object due to @code{matlab_lang_makeValidName} +@item null, inside a numeric array @tab @code{NaN} +@item null, inside a non-numeric array @tab empty double array @code{[]} +@item Array, of different data types @tab cell array +@item Array, of Booleans @tab logical array +@item Array, of Numbers @tab double array +@item Array, of Strings @tab cell array of character vectors (@code{cellstr}) +@item Array of Objects, same field names @tab struct array +@item Array of Objects, different field names @tab cell array of scalar structs +@end multitable Examples: @@ -498,7 +484,8 @@ @end group @group -jsondecode ('@{"nu#m#ber": 7, "s#tr#ing": "hi"@}', 'ReplacementStyle', 'delete') +jsondecode ('@{"nu#m#ber": 7, "s#tr#ing": "hi"@}', ... + 'ReplacementStyle', 'delete') @result{} scalar structure containing the fields: number = 7 @@ -520,26 +507,27 @@ #if defined (HAVE_RAPIDJSON) int nargin = args.length (); + // makeValidName options are 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"); + if (! args(0).is_string ()) + error ("jsondecode: JSON_TXT must be a character string"); - std::string json = args (0).string_value (); + 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 <rapidjson::kParseNanAndInfFlag>(json.c_str ()); + d.Parse <rapidjson::kParseNanAndInfFlag> (json.c_str ()); if (d.HasParseError ()) - error("jsondecode: Parse error at offset %u: %s\n", - static_cast<unsigned int> (d.GetErrorOffset ()), - rapidjson::GetParseError_En (d.GetParseError ())); + error ("jsondecode: parse error at offset %u: %s\n", + static_cast<unsigned int> (d.GetErrorOffset ()) + 1, + rapidjson::GetParseError_En (d.GetParseError ())); return decode (d, args.slice (1, nargin - 1)); @@ -551,3 +539,16 @@ #endif } + +/* +FIXME: Need BIST tests for decoding each data type. +##%!testif HAVE_RAPIDJSON + +## Input validation tests +%!testif HAVE_RAPIDJSON +%! fail ("jsondecode ()"); +%! fail ("jsondecode ('1', 2)"); +%! fail ("jsondecode (1)", "JSON_TXT must be a character string"); +%! fail ("jsondecode ('12-')", "parse error at offset 3"); + +*/
--- a/libinterp/corefcn/jsonencode.cc Wed Sep 16 13:15:35 2020 -0700 +++ b/libinterp/corefcn/jsonencode.cc Thu Sep 17 05:07:02 2020 -0700 @@ -44,7 +44,7 @@ //! Encodes a scalar Octave value into a numerical JSON value. //! -//! @param writer RapidJSON's writer that is responsible for generating json. +//! @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. //! @@ -52,36 +52,36 @@ //! //! @code{.cc} //! octave_value obj (7); -//! encode_numeric (writer, obj,true); +//! encode_numeric (writer, obj, true); //! @endcode template <typename T> void -encode_numeric (T& writer, const octave_value& obj, const bool& ConvertInfAndNaN) +encode_numeric (T& writer, const octave_value& obj, + const bool& ConvertInfAndNaN) { - double value = obj.scalar_value (); + 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. + // 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<double>::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 ()) + // Possibly write NULL for non-finite values (-Inf, Inf, NaN, NA) + else if (ConvertInfAndNaN && ! octave::math::isfinite (value)) writer.Null (); else if (obj.is_double_type ()) writer.Double (value); else - error ("jsonencode: Unsupported type."); + error ("jsonencode: unsupported type"); } //! Encodes character vectors and arrays into JSON strings. //! -//! @param writer RapidJSON's writer that is responsible for generating json. +//! @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. @@ -90,7 +90,7 @@ //! //! @code{.cc} //! octave_value obj ("foo"); -//! encode_string (writer, obj,true); +//! encode_string (writer, obj, true); //! @endcode template <typename T> void @@ -98,13 +98,14 @@ 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. + // Handle the special case where 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 = ""; @@ -127,16 +128,16 @@ 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 + // 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 + // 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 every dimension that equals 1 until we reach the 2-D vector for (int i = level; i < ndims - 1; ++i) writer.StartArray (); @@ -151,7 +152,7 @@ // 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) + if (original_dims(level) == 1 && level != 1) { writer.StartArray (); encode_string (writer, array, original_dims, level + 1); @@ -168,7 +169,7 @@ 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 + // 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. @@ -199,7 +200,7 @@ //! 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 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. //! @@ -215,9 +216,10 @@ { octave_map struct_array = obj.map_value (); octave_idx_type numel = struct_array.numel (); + bool is_array = (numel > 1); string_vector keys = struct_array.keys (); - if (numel > 1) + if (is_array) writer.StartArray (); for (octave_idx_type i = 0; i < numel; ++i) @@ -231,13 +233,13 @@ writer.EndObject (); } - if (numel > 1) + if (is_array) writer.EndArray (); } //! Encodes a Cell Octave value into a JSON array //! -//! @param writer RapidJSON's writer that is responsible for generating json. +//! @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. //! @@ -263,7 +265,7 @@ //! Encodes a numeric or logical Octave array into a JSON array //! -//! @param writer RapidJSON's writer that is responsible for generating json. +//! @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. @@ -343,7 +345,7 @@ break; // Create the dimensions that will be used to call "num2cell" - // We called "num2cell" to divide the array to smaller sub arrays + // 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. @@ -374,7 +376,7 @@ //! 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 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. //! @@ -406,6 +408,7 @@ // In order to convert it we will need to disable the // "Octave:classdef-to-struct" warning and re-enable it. { + // FIXME: Need to save and restore current state of warning. set_warning_state ("Octave:classdef-to-struct", "off"); encode_struct (writer, obj.scalar_map_value ().getfield ("map"), ConvertInfAndNaN); @@ -413,131 +416,103 @@ } else if (obj.isobject ()) { + // FIXME: Need to save and restore current state of warning. 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."); + 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{}) +@deftypefn {} {@var{JSON_txt} =} jsonencode (@var{object}) +@deftypefnx {} {@var{JSON_txt} =} jsonencode (@dots{}, "ConvertInfAndNaN", @var{TF}) +@deftypefnx {} {@var{JSON_txt} =} jsonencode (@dots{}, "PrettyWriter", @var{TF}) -Encode Octave's data types into JSON text. +Encode Octave data types into JSON text. + +The input @var{object} is an Octave variable to encode. -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}. +The output @var{JSON_txt} 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{"ConvertInfAndNaN"} is true then @code{NaN}, +@code{NA}, @code{-Inf}, and @code{Inf} values will be converted to +@qcode{"null"} in the output. If it is false then they will remain as 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. +and written without whitespace. The default value for this option is false. --NOTES: +Programming 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. +classdef objects are first converted to structs and then encoded. @item -To preserve the escape characters (e.g. "\n"), use double-quote strings. +To preserve escape characters (e.g., @qcode{"\n"}), use single-quoted strings. @item -Every character after the null character ("\0") in double-quoted strings will -be dropped during encoding. +Every character after the null character (@qcode{"\0"}) in a double-quoted +string will be dropped during encoding. @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. +Encoding and decoding an array is not guaranteed to preserve the dimensions +of the array. For example, if a row vector is encoded and decoded then the +result will be 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"}. +Encoding and decoding is not guaranteed to preserve the Octave data type +because JSON supports fewer data types than Octave. For example, if you +encode an @code{int8} and then decode it, you will get a @code{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 +@multitable @columnfractions 0.50 0.50 +@headitem Octave data type @tab JSON data type +@item logical scalar @tab Boolean +@item logical vector @tab Array of Boolean, reshaped to row vector +@item logical array @tab nested Array of Boolean +@item numeric scalar @tab Number +@item numeric vector @tab Array of Number, reshaped to row vector +@item numeric array @tab nested Array of Number +@item @code{NaN}, @code{NA}, @code{Inf}, @code{-Inf}@* +when @qcode{"ConvertInfAndNaN" = true} @tab @qcode{"null"} +@item @code{NaN}, @code{NA}, @code{Inf}, @code{-Inf}@* +when @qcode{"ConvertInfAndNaN" = false} @tab @qcode{"NaN"}, @qcode{"NaN"}, @qcode{"Infinity"}, @qcode{"-Infinity"} +@item empty array @tab @qcode{"[]"} +@item character vector @tab String +@item character array @tab Array of String +@item empty character array @tab @qcode{""} +@item cell scalar @tab Array +@item cell vector @tab Array, reshaped to row vector +@item cell array @tab Array, flattened to row vector +@item struct scalar @tab Object +@item struct vector @tab Array of Object, reshaped to row vector +@item struct array @tab nested Array of Object +@item classdef object @tab Object +@end multitable Examples: @example @group -jsonencode ([1 NaN; 3 4]) +jsonencode ([1, NaN; 3, 4]) @result{} [[1,null],[3,4]] @end group @group -jsonencode ([1 NaN; 3 4], "ConvertInfAndNaN", false) +jsonencode ([1, NaN; 3, 4], "ConvertInfAndNaN", false) @result{} [[1,NaN],[3,4]] @end group @@ -554,7 +529,7 @@ @end group @group -jsonencode ([true; false], "ConvertInfAndNaN", false, "PrettyWriter", true) +jsonencode ([true; false], "PrettyWriter", true) @result{} ans = [ true, false @@ -594,7 +569,7 @@ int nargin = args.length (); // jsonencode has two options 'ConvertInfAndNaN' and 'PrettyWriter' - if (! (nargin == 1 || nargin == 3 || nargin == 5)) + if (nargin != 1 && nargin != 3 && nargin != 5) print_usage (); // Initialize options with their default values @@ -604,42 +579,42 @@ for (octave_idx_type i = 1; i < nargin; ++i) { if (! args(i).is_string ()) - error ("jsonencode: Option must be character vector"); + error ("jsonencode: option must be a string"); if (! args(i+1).is_bool_scalar ()) - error ("jsonencode: Value for options must be logical scalar"); + error ("jsonencode: option value must be a logical scalar"); std::string option_name = args(i++).string_value (); - if (octave::string::strcmpi(option_name, "ConvertInfAndNaN")) + if (octave::string::strcmpi (option_name, "ConvertInfAndNaN")) ConvertInfAndNaN = args(i).bool_value (); - else if (octave::string::strcmpi(option_name, "PrettyWriter")) + else if (octave::string::strcmpi (option_name, "PrettyWriter")) PrettyWriter = args(i).bool_value (); else - error ("jsonencode: Valid options are \'ConvertInfAndNaN\'" - " and \'PrettyWriter\'"); + error ("jsonencode: " + R"(Valid options are "ConvertInfAndNaN" and "PrettyWriter")"); } // FIXME: RapidJSON 1.1.0 (2016-08-25) is the latest release (2020-08-18) // and does not support the "PrettyWriter" option. Once a newer // RapidJSON version is released and established with major // distributions, make that version a requirement. - #if ! defined (HAVE_RAPIDJSON_DEV) - if (PrettyWriter) - { - warn_disabled_feature ("jsonencode", - "the \'PrettyWriter\' option of RapidJSON"); - PrettyWriter = false; - } - #endif +# if ! defined (HAVE_RAPIDJSON_DEV) + if (PrettyWriter) + { + warn_disabled_feature ("jsonencode", + R"(the "PrettyWriter" option of RapidJSON)"); + PrettyWriter = false; + } +# endif rapidjson::StringBuffer json; if (PrettyWriter) { - #if defined (HAVE_RAPIDJSON_DEV) - rapidjson::PrettyWriter<rapidjson::StringBuffer, rapidjson::UTF8<>, - rapidjson::UTF8<>, rapidjson::CrtAllocator, - rapidjson::kWriteNanAndInfFlag> writer (json); - encode (writer, args(0), ConvertInfAndNaN); - #endif +# if defined (HAVE_RAPIDJSON_DEV) + rapidjson::PrettyWriter<rapidjson::StringBuffer, rapidjson::UTF8<>, + rapidjson::UTF8<>, rapidjson::CrtAllocator, + rapidjson::kWriteNanAndInfFlag> writer (json); + encode (writer, args(0), ConvertInfAndNaN); +# endif } else { @@ -659,3 +634,24 @@ #endif } + +/* +FIXME: Need BIST tests for encoding each data type +##%!testif HAVE_RAPIDJSON + + +## Input validation tests +%!testif HAVE_RAPIDJSON +%! fail ("jsonencode ()"); +%! fail ("jsonencode (1, 2)"); +%! fail ("jsonencode (1, 2, 3, 4)"); +%! fail ("jsonencode (1, 2, 3, 4, 5, 6)"); +%! fail ("jsonencode (1, 2, true)", "option must be a string"); +%! fail ("jsonencode (1, 'string', ones (2,2))", "option value must be a logical scalar"); +%! fail ("jsonencode (1, 'foobar', true)", 'Valid options are "ConvertInfAndNaN"'); + +%!testif HAVE_RAPIDJSON; ! __have_feature__ ("HAVE_RAPIDJSON_DEV") +%! fail ("jsonencode (1, 'PrettyWriter', true)", ... +%! "warning", 'the "PrettyWriter" option of RapidJSON was unavailable'); + +*/