--- fromJSON.m	2020-10-31 18:00:28.273031100 +0100
+++ test/fromJSON.m	2020-11-01 14:57:17.525262400 +0100
@@ -1,10 +1,10 @@
-## Copyright (C) 2019 Ketan M. Patel
-## 
+## Copyright (C) 2019-2020 Ketan M. Patel
+##
 ## This program 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.
-## 
+##
 ## This program 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
@@ -14,33 +14,46 @@
 ## along with this program.  If not, see
 ## <https://www.gnu.org/licenses/>.
 
-## -*- texinfo -*- 
-## @deftypefn {} {@var{obj} =} fromJSON (@var{str},@var{sarray})
+## -*- texinfo -*-
+## @deftypefn {} {@var{obj} =} fromJSON (@var{str}, @var{sarray})
+## Convert a JSON string into a native Octave object.
+##
+## Input arguments:
+##
+## @itemize
+## @item
+## @var{str} is a JSON input string.
+## @end item
+##
+## @item
+## It is recommended to supply optional argument @var{sarray} set to `true`
+## for parsing JSON strings with structured, numerical data array, but `false`
+## for parsing mixed-class structures such as config JSON or HTTP payload.
+## The default value is `true`
+## @end item
+## @end itemize
 ##
-##  Converts any JSON string @var{str} into an applicable native Octave object:
-##  number, logical, string, struct.  
-##  'null' is converted to NaN and any numerical string >= +/-1e308 is converted
-##  to +/-Inf.   Unrecognizable, unquoted JSON fragments are returned as Octave string.
-##
-##  Where possible, nested JSON array will be returned as matrix or ND array.
-##  String arrays, mixed class and mixed size JSON arrays will returns as 
-##  Octave cell arrays.
-##
-##  For JSON object with member arrays, if @var{sarray} is TRUE (default) and 
-##  if possible, an Octave struct array is returned; otherwise, a scalar struct 
-##  with member arrays is returned.   
-##
-##  Setting @var{sarray} to TRUE is recommended for parsing JSON strings with
-##  structured, numerical data array, but FALSE for parsing mixed-class
-##  structures such as config JSON or HTTP payload.
-##
-##  Complex number in JSON object format i.e. {"re":..., "im": ...} or 
-##  {"real":..., "imag":...} will be converted a complex number matrix, 
-##  provided that both "re" and "im" members are present and of equal size.
-##
-##  CAUTION:  In most cases, `fromJSON` will sucessfully parse the output 
-##            of `jsonencode`.  A very important exception, however, is
-##            ND arrays (greater than 2 dims), which is handled differently.
+## Output:
+##
+## @var{obj} is a native Octave object.  Numbers, logicals, strings, and
+## structs are converted directly.  'null' is converted to NaN and any
+## numerical string >= +/-1e308 is converted to +/-Inf.  Unrecognizable,
+## unquoted JSON fragments are returned as Octave strings.
+##
+## Where possible, nested JSON array will be returned as matrix or ND array.
+## String arrays, mixed class and mixed-size JSON arrays will be returned
+## as Octave cell arrays.
+##
+## For JSON object with member arrays, if @var{sarray} is TRUE (default) and
+## if possible, an Octave struct array is returned; otherwise, a scalar struct
+## with member arrays is returned.
+##
+## Complex numbers in JSON object format, i.e. @{"re":..., "im": ...@} or
+## @verbatim
+##   @{"real":..., "imag":...@}
+## @end verbatim
+## will be converted a complex number matrix,
+## provided that both "re" and "im" members are present and of equal size.
 ##
 ## @seealso{toJSON}
 ## @end deftypefn
@@ -54,60 +67,60 @@
 function [obj] = fromJSON (str='', SARRAY=[]);
 
   if ~( isnumeric(SARRAY) || isbool(SARRAY) );
-    _warn_("Invalid SARRAY");
+    _warn_("fromJON: invalid SARRAY");
     SARRAY = [];
   endif
-  
+
   % set defaults
   ischar(str)      || (str='');
-  ~isempty(SARRAY) || (SARRAY=true); 
-  
+  ~isempty(SARRAY) || (SARRAY=true);
+
   try
     str = regexprep(str,'^[\s,]*','');
   catch
-    error("Invalid UTF-8 encoding of JSON string");    
+    error("Invalid UTF-8 encoding of JSON string");
   end_try_catch
-  
-  if( isempty(str) );  
+
+  if( isempty(str) );
     obj = [];
   else
-    wstate = warning('off',"Octave:num-to-str");    
+    wstate = warning('off',"Octave:num-to-str");
     obj = _fromjson_(str,SARRAY);
     warning(wstate);
   endif
-  
+
 endfunction
 
 function [obj] = _fromjson_(str,SARRAY);
-    
+
     obj = {};
     do;
       [obj{end+1} str] = _str2obj_(str,SARRAY);
     until( isempty(str) );
-    
+
     if( numel(obj) == 1 );
       obj = obj{1};
     elseif( ~isstruct(obj{1}) || SARRAY );
       obj = _ndarray_(obj);
     endif
-    
+
 endfunction
 
-function [obj,remain] = _str2obj_(str,SARRAY);  
+function [obj,remain] = _str2obj_(str,SARRAY);
 
     [btype, block, remain] = _get_block_(str);
     remain = regexprep(remain,'^[\s,]*','');     % keep this HERE, not in _get_block_
-    
+
     switch( btype );
       case '['; obj = _to_array_  (block,SARRAY);
       case '{'; obj = _to_struct_ (block,SARRAY);
       case '"'; obj = _to_string_ (block);
-      
+
       otherwise;  eval(['obj=[' _rep_(block, Inf) '];'], ...
-                       ['_warn_("Invalid frag", (obj=block));']);
+                       ['_warn_("fromJSON: invalid frag", (obj=block));']);
     endswitch
-    
-endfunction  
+
+endfunction
 
 function [btype, block, remain] = _get_block_ (str);
 
@@ -118,13 +131,13 @@
                       idx = [1 -(idx+1)];
                       btype = '"';
 
-      otherwise;  [block,remain] = strtok(str,',');  
+      otherwise;  [block,remain] = strtok(str,',');
                   return;
     endswitch
-    
+
     % exclude escaped-brackets (e.g.'\[', '\{', '\"', etc)
     tmp = abs(idx); tmp(2:end)--;
-    idx(str(tmp) == '\') = [];   
+    idx(str(tmp) == '\') = [];
 
     if( numel(idx) == 1 );                  % unterminated block
       close = numel(str)+1;
@@ -132,23 +145,23 @@
       close = -idx(2);
     else                                    % nested block
       idx   = idx([~,i] = sort(abs(idx)));  % sort by abs(idx)
-      count = cumsum(sign(idx));        
+      count = cumsum(sign(idx));
       close = -idx(find(count==0, 1));
       close || (close = numel(str)+1);     % if necessary, autoclose at end of str
     end
-    
-    close <= numel(str) || _warn_("Unclosed block", str(1:min(20,end)));
-    
+
+    close <= numel(str) || _warn_("fromJSON: unclosed block", str(1:min(20,end)));
+
     block  = str(2 : close-1);
     remain = str(close+1 : end);
- 
+
 endfunction
 
 %========================= str2... =======================
 
 function [mat] = _to_array_(str,SARRAY);
-  
-    % look for >2D array or assignment (unsafe)  
+
+    % look for >2D array or assignment (unsafe)
     ok = ~( regexp(str,'^\s*\[\s*\[') || strfind(str,'=') );  % <== faster done seperately
 
     if( ok );
@@ -167,39 +180,39 @@
     elseif isstruct(mat);   % double-check struct array for special members
       mat = structfun(@_specials_,mat,'uniformoutput',false);
     endif
-   
-endfunction 
+
+endfunction
 
 function [obj] = _to_struct_(str,SARRAY);
 
     frag = str(1:min(end,20)); % keep frag for warning msg
     vals = keys = {};
-    
+
     try; while( str );       % may fail for incomplete JSON struct string
-      [key         str] = regexp(str,'^\s*"?(.*?)"?\s*:\s*(.*)$','tokens','once'){:}; 
+      [key         str] = regexp(str,'^\s*"?(.*?)"?\s*:\s*(.*)$','tokens','once'){:};
       [vals{end+1} str] = _str2obj_(str,SARRAY);
       keys{end+1}       = eval(['"' key '"']);  % huh!?  clean up key of esc-codes
     endwhile; catch;
-      _warn_("Malformed struct frag", ['{' strtrim(frag) '...']);
+      _warn_("fromJSON: malformed struct frag", ['{' strtrim(frag) '...']);
     end_try_catch
 
     try;
       SARRAY || isempty(vals) || error("don't");
       obj = struct([keys; _num2cell_(vals)]{:});   % may fail for mixed-size vals
     catch;
-      for i = 1:numel(vals);  
-        obj.(keys{i}) = vals{i};            
+      for i = 1:numel(vals);
+        obj.(keys{i}) = vals{i};
       endfor;
     end_try_catch
-    
+
     obj = _specials_(obj);
-    
+
 endfunction
 
 function [obj] = _to_string_(str);
-    if( regexp(str,'^\s*@') ); 
+    if( regexp(str,'^\s*@') );
       try    obj = str2func(str);
-      catch  _warn_("Invalid inline function", str);
+      catch  _warn_("fromJSON: invalid inline function", str);
       end_try_catch;
     else
       obj = eval(['"' str '"']);  % huh!?  used to eval escaped chars (e.g. '\"')
@@ -215,12 +228,12 @@
   elseif isinf(arg)
     arg = {'null','NaN ','e308','*Inf','Infinity','Inf     '};  % keep empty spaces
   endif
-  
+
   do
     str = strrep(str,arg{1:2},'overlaps',false);
     arg(1:2) = [];
   until isempty(arg);
-  
+
 endfunction
 
 function [c] = _num2cell_(c);
@@ -236,34 +249,34 @@
 function [c] = _ndarray_(c);  % try to make ND array (either mat or cell)
 
   sz = size(e = c{1}); t = class(e);
-  
+
   if ~( ~ischar(e) && sz && cellfun(@(v)isa(v,t)&&size(v)==sz, c) );
     return;
   elseif( isscalar(e) );     % try matrix conversion, ok to fail if not uniform class
-    try c = [c{:}]; end;   
+    try c = [c{:}]; end;
   elseif( isvector(e) );     % cell array of mat or cell array
     c = reshape([c{:}], numel(e), numel(c)).';
   else                    % make ND array
     index.type = "()";
     index.subs = repmat({':'},1,ndims(e)+1);
     i = numel(c);
-    
+
     do
       index.subs{end} = i;
       e = subsasgn(e,index,c{i});
     until (--i) == 1;
     c = e;
-    
+
   endif
-    
+
 endfunction
 
 function [s] = _specials_(s);   % convert special structures
 
   if( ~isstruct(s) ); return; endif;
-  
+
   keys = __fieldnames__(s);
-  
+
   if( ismember(keys,{'real','imag'}) ); _to_complex_([s.real],[s.imag]);
   elseif( ismember(keys,{'re','im'}) ); _to_complex_([s.re  ],[s.im  ]);
   endif
@@ -275,7 +288,7 @@
       s = reshape(complex(re,im),sz);
     endif
   endfunction
-  
+
 endfunction
 
 % for max conversion speeed, gaurd against lazy JSON (unquoted keys) of common structures
@@ -289,7 +302,7 @@
 
 %!test  ## input validation
 %! assert(fromJSON(),[]); % ok, reference
-%!warning <Invalid SARRAY> fromJSON([],struct);
+%!warning <invalid SARRAY> fromJSON([],struct);
 %!
 %! bad = {4,{},@sin,struct(),'',false,0,[1,2,3]};
 %! assert(all(cellfun(@(i)isempty(fromJSON(i)),bad)));  % all bad, return []
@@ -408,7 +421,7 @@
 %! assert(fromJSON('{a:3,"a":5}'),struct("a",5))
 
 %!test   ## empty object key-val
-%! obj=fromJSON('{a:3,,,    ,     "b"   :5}');     
+%! obj=fromJSON('{a:3,,,    ,     "b"   :5}');
 %! assert(obj,struct("a",3,"b",5))
 
 %!test ## struct of vector
@@ -426,13 +439,13 @@
 %! assert(fromJSON('[{a:1},{a:2}],[{a:3},{a:4}]',false), num2cell(struct('a',{1 2;3 4})));
 
 %!test  ## struct with mixed-size arrays (will not honor SARRAY=true)
-%! assert(fromJSON('{"a":[1,2],"b":[3,4,5]}',true), struct("a",[1,2],"b",[3,4,5])) 
+%! assert(fromJSON('{"a":[1,2],"b":[3,4,5]}',true), struct("a",[1,2],"b",[3,4,5]))
 
 %!test  ## struct with number and string (guard against turing string into char array)
-%! assert(fromJSON('{"a":1,"b":"hello"}',true), struct("a",1,"b","hello")) 
+%! assert(fromJSON('{"a":1,"b":"hello"}',true), struct("a",1,"b","hello"))
 
 %!test  ## struct with empty array  (gaurd against returning empty struct array)
-%! assert(fromJSON('{"a":3,"b":[]}',true), struct("a",3,"b",[])) 
+%! assert(fromJSON('{"a":3,"b":[]}',true), struct("a",3,"b",[]))
 
 %!test  ## incomplete struct
 %! warning('off','all');
@@ -442,12 +455,12 @@
 %! assert(fromJSON('{"a":{"b":[1,2,3]}}',true ), struct("a",num2cell(struct("b",{1,2,3}))))  % <== struct array
 %! assert(fromJSON('{"a":{"b":[1,2,3]}}',false), struct("a",struct("b",[1,2,3])))            % <== struct with array b
 
-%!test  ## struct with mixed class array 
+%!test  ## struct with mixed class array
 %! assert(fromJSON('{"b":[1,2,{c:4}]}',true),struct('b',{1,2,struct('c',4)}));
 %! s.b = {1,2,struct("c",4)};
 %! assert(fromJSON('{"b":[1,2,{c:4}]}',false),s)
 
-%!test ## 2x2 array of struct 
+%!test ## 2x2 array of struct
 %! obj=fromJSON('[[{a:1},{a:3}],[{a:2},{a:4}]]');
 %! assert(obj,struct('a',{1 3;2 4}));
 
@@ -459,7 +472,7 @@
 %! obj=fromJSON('{a:[[[1,3],[2,4]],[[11,13],[12,14]]]}');
 %! assert(obj,struct('a',num2cell(reshape([1:4 11:14],2,2,2))));
 
-%!test ## ND array of struct 
+%!test ## ND array of struct
 %! obj=fromJSON('[[[{a:1},{a:3}],[{a:2},{a:4}]],[[{a:11},{a:13}],[{a:12},{a:14}]]]');
 %! assert(obj,struct('a',num2cell(reshape([1:4 11:14],2,2,2))));
 
@@ -526,10 +539,10 @@
 %! assert(fromJSON('"[java.math.BigDecimal]"'),'[java.math.BigDecimal]');
 
 %!test   ##  warnings
-%!warning <Invalid frag>   fromJSON('garbage');
-%!warning <Malformed>      fromJSON('{a:3,b}');
-%!warning <Unclosed>       fromJSON('{a:3,b:4');
-%!warning <Invalid frag>   fromJSON('@nofunc'); ## looks like fn, but is UNQUOTED string
+%!warning <invalid frag>   fromJSON('garbage');
+%!warning <malformed>      fromJSON('{a:3,b}');
+%!warning <unclosed>       fromJSON('{a:3,b:4');
+%!warning <invalid frag>   fromJSON('@nofunc'); ## looks like fn, but is UNQUOTED string
 
 %!test  %% jsondecode's Arrays with the same field names in the same order.
 %! json = ['[', ...
