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');
+
+*/