## Copyright (C) 2019-2021 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
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see
## <https://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {} {@var{obj} =} fromJSON (@var{str})
## @end deftypefn
## @deftypefn {} {@var{obj} =} fromJSON (@var{str}, @var{sarray})
##
## Convert a JSON string into a native Octave object.  It especially strives to convert
## numerical JSON arrays into Octave vector, matrix or ND array.  Special
## provisions are made to recognize +/-Inf, NaN and complex numbers, which
## are conventionally not permited is JSON string.
##
## Input arguments:
##
## @itemize
## @item
## @var{str}, a JSON string.
## @end item
##
## @item
## @var{sarray}, (default TRUE) logical that  determines how JSON member arrays are parsed. 
## Setting to FALSE is recommended for safer parsing of
## non-numerical, mixed-class or mixed-size arrays JSON payloads.
##
## @end item
## @end itemize
##
## Output:
##
## @var{obj} is a native Octave object.  JSON number, logical, array, and object strings are 
## converted to Octave numbers, bools, vectors and structs respectively. Quoted or unrecognizable 
## JSON fragments return as strings.  
##
## When @var{sarray} is TRUE, JSON arrays return as Octave vector, matrix or ND arrays 
## if possible, else some combination of vectors/matrix and cell array.  
##
## When @var{sarray} is FALSE, all JSON member arrays return as (nested) Octave cell arrays.
##
## Special numbers:
##
## The specification for JSON does not allow +/-Inf, NaN or complex numbers;
## nevertheless, provisions are made here to enable these important numbers:
##
## @itemize
## @item
## JSON number strings '+/-1e308' are rounded to +/-Inf.  
## @end item
##
## @item
## Unquoted JSON string 'null' is converted to NaN.  
## @end item
##
## @item
## JSON object, or array thereof, with exclusive members "real" and "imag" (or "re" and "im")
## will be converted to a Octave complex number, vector or matrix, respectively. 
##
## @w{    e.g. '@{"re":3,"im":1@}'     => 3 + 1i               }
## @w{         '[1,@{"re":3,"im":1@}]' => [ 1 + 0i, 3 + 1i ]   }
##
## @end item
## @end itemize
##
## @seealso{toJSON}
## @end deftypefn

## v2.7.2
## Author: Ketan M. Patel <kmpatel@roc-photonics.com>
## Created: 2019-06-01
## Updates:
## 2020-10-22 Refactored and documented (KMP)
## 2020-10-27 Updated for Octave 7 compatibility (KMP)
## 2021-12-15 Bug fix: whitespace in array, colon in string (KMP)
##            Feature change: SARRAY == FALSE always returns cell array
##            Feature change: Object with arrays now returns struct 
##                            with array, instead of struct array
##                            Array of objects still return struct array
## 2021-12-17 Bug fix: recursive scan of struct for deep complex number objects
## 2021-12-18 Got rid of escape char evaluation in _to_string, mistakeningly
##            added to help with BIST

function [obj] = fromJSON ( str='', SARRAY=[] );

  if ~( isnumeric(SARRAY) || isbool(SARRAY) );
    _warn_("invalid SARRAY");
    SARRAY = [];
  endif

  % set defaults
  ischar(str)      || (str='');
  ~isempty(SARRAY) || (SARRAY=true);

  try   % check for encoding error
    regexp(str,'.$');
  catch err
    if( regexpi(err.message, "invalid(.*)UTF-8") );
      error("Invalid UTF-8 encoding of JSON string");
    else
      error(err);
    end
  end_try_catch

  wstate = warning('off',"Octave:num-to-str");
  obj    = _fromjson_(str,SARRAY);
  warning(wstate);

endfunction

function [obj, remain] = _fromjson_ ( str, SARRAY );

    [btype, block, remain] = _get_block_(str);

    switch( btype );
      case '[';  obj = _to_array_  (block, SARRAY);
      case '{';  obj = _to_struct_ (block, SARRAY);
      case '"';  obj = _to_string_ (block, SARRAY);
      otherwise; obj = _to_number_ (block, SARRAY);
    endswitch

    remain = regexprep(remain,'^[\s,]*',''); %!!! keep this HERE, not in _get_block_
    
endfunction
    
function [btype, block, remain] = _get_block_ (str);

    [open, btype] = regexp(str,'[^\s,]','once','start','match');
    
    switch( btype );     % find ALL open/close bracket positions
      case '[';  idx = [strfind(str,'[') -strfind(str,']')];
      case '{';  idx = [strfind(str,'{') -strfind(str,'}')];
      
      case {'"',"'"}; idx = strfind(str,btype);
                      idx(2:end) *= -1;
                      btype = '"';  % treat single-quote as double-quote (lazy JSON)

      otherwise;  [block,remain] = strtok(str(open:end),',');
                  return;
    endswitch

    % exclude escaped-brackets (e.g.'\[', '\{', '\"', etc)
    tmp = abs(idx); tmp(2:end)--;
    idx(str(tmp) == '\') = [];

    if( numel(idx) == 1 );                    % handle unterminated block
        close = numel(str)+1;
        _warn_(btype, str);
        
    elseif( idx(2) < 0 );                     % handle simple, non-nested block
        close = -idx(2);
        
    else                                      % handle nested block
        idx   = idx([~,i] = sort(abs(idx)));  % sort idx by abs(idx)
        count = cumsum(sign(idx));
        close = -idx(find(count==0, 1));
        close || (close = numel(str)+1);       % if necessary, auto-close block
    end
    
    block  = str(   open+1 : close-1 );
    remain = str(  close+1 : end     );

endfunction

function [arr] = _to_array_ ( str, SARRAY );

    ok = SARRAY && numel(strfind(str,'[')) < 3 ...    # ND array not OK
                && isempty(strfind(str,'='));         # apparent assignment definitely not OK
    if( ok );
    
      arr = regexprep(_rep_(str, Inf), ',(?=\s*\[)',';');  % JSON to Octave array syntax
      
      % test for JSON object ( exclude objects with strings )
      if( strfind(str,'{') && isempty(regexp(arr,':\s*[''"]','once')) ); 
          arr = [ _lazyJSON_ "; arr=_deep_complex_([" _rep_(arr, struct) "])" ]; 
      else
          arr = [ "arr=[" arr "]" ];
      endif
      
      eval([ arr "; ok = isnumeric(arr) || isstruct(arr);" ], "ok=false;");
      
    endif

    if( ~ok );   % this can be slow, avoid getting here if possible    

      arr = {};
      
      while( str );  % grind thru string, one array element at a time
          [arr{end+1} str] = _fromjson_(str,SARRAY);
      endwhile;
      
      if( SARRAY );
          switch numel(arr);
            case 0;     arr = []; 
            case 1;     arr = arr{1};
            otherwise;  arr = _ndarray_(arr);
          endswitch
      endif
        
    endif
  
endfunction

function [obj] = _to_struct_ ( str, SARRAY );

    obj = struct();
    
    while( (i = regexp(str,':','once')) );
    
      k             = regexprep (str(1:i-1), '^\s*[''"]?|[''"]?\s*$', '');
      [obj.(k) str] = _fromjson_(str(i+1:end), SARRAY);
         
    endwhile      

    str && _warn_('}', [str '}']);
    obj = _complex_( obj );   % _deep_complex_ not necessary here
    
endfunction

function [str] = _to_string_ ( str, SARRAY );
  
    if( SARRAY && (numel(str) > 2 && str(1:2) == '@') );
        try    str = str2func(str(2:end));
        catch  _warn_('@', str); 
        end_try_catch;
    endif
    
endfunction

function [num] = _to_number_( str, SARRAY );
  
    if( strfind(str,'=') || strcmp('num',str) );   % guard against assignment expr
        num = 'error()';
    else
        num = ['num=[' _rep_(str,Inf) '];'];
    endif
    
    eval(num, ['_warn_("invalid frag", (num=str));']);
    
endfunction

%========================= helpers =======================

function [str] = _rep_ ( str, arg );

  if isstruct(arg);
      arg = {'{','struct(',':',',','}',')'};
  else
      arg = {'null','NaN ','e308','e999','Infinity','Inf     '};  % keep empty spaces
  endif

  do
    str = strrep(str,arg{1:2},'overlaps',false);
    arg(1:2) = [];
  until isempty(arg);

endfunction

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;
  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] = _deep_complex_(s);

  % try to convert struct array to complex number array
  % else, recursively try conversion on each struct member

  if( isstruct(s) && isstruct( s=_complex_(s) ) );
     s = structfun(@_deep_complex_,s,'uniformoutput',false);
  endif
  
endfunction

function [s] = _complex_ ( s );   % convert complex number structures

  if( (numel( keys=__fieldnames__(s) ) == 2) &&
      (ismember(k={'real','imag'}, keys) || ismember(k={'re','im'}, keys)) )
      
    re = [s.(k{1})];
    im = [s.(k{2})];
    
    if( isnumeric(re) && isnumeric(im) && size_equal(re,im) );
        s = reshape( complex(re,im), ifelse(isscalar(s),size(re),size(s)) );
    endif
  endif
  
endfunction

% for max conversion speeed, gaurd against common lazy JSON (unquoted keys)
function [str] = _lazyJSON_ ( );
  str = 'x="x";y="y";z="z";real=re="re";imag=im="im";r="r";rho="rho";theta="theta";phi="phi";';
endfunction

function [out] = _warn_ ( msg, frag='' );
  
  if( numel(msg) == 1 );
    switch(msg);
       case '{'; msg = "unclosed object";
       case '['; msg = "unclosed array";
       case '"'; msg = "unclosed quote";
       case '}'; msg = "malformed structure";
       case '@'; msg = "invalid inline func string";
     endswitch
  endif
  
  numel(frag)>40 && (frag = [frag(1:17) ' ... ' frag(end-17:end)]);
  out = warning(sprintf("fromJSON.m: %s %s\n",msg,frag));
  
endfunction


################################################################################
###############################   BIST   #######################################
################################################################################

%!test	##	input validation
%! assert( fromJSON(),[]); % ok, reference
%!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 []

%!test	##	number
%! assert( fromJSON('4'),4)

%!test	##	number string
%! assert( fromJSON('"string"'),"string")

%!test	##	bool
%! assert( fromJSON('true'),true)

%!test	##	round up to Inf
%! assert( fromJSON('1e308'),      Inf)
%! assert( fromJSON('-1e308'),    -Inf)

%!test  ## do NOT round up to Inf
%! assert( fromJSON('1e307'),    1e307)
%! assert( fromJSON('-1e307'),  -1e307)

%!test  ## automatically parse as Inf
%! assert( fromJSON('1e309'),      Inf)
%! assert( fromJSON('-1e309')     -Inf)
%! assert( fromJSON('1e999'),      Inf)
%! assert( fromJSON('-1e999'),    -Inf)

%!test	##	null
%! assert( fromJSON( 'null' ),NaN)

%!test  ## in case JSON spec commitee gets head out of ass
%! assert( fromJSON('Inf'),        Inf)
%! assert( fromJSON('-Inf'),      -Inf)
%! assert( fromJSON('Infinity'),   Inf)
%! assert( fromJSON('-Infinity'), -Inf)
%! assert( fromJSON('NaN'),        NaN)
%! assert( fromJSON('nan'),        NaN)

%!test	##	quoted null,Inf,etc (guard against false-positive, leave alone)
%! assert( fromJSON('"null"'),"null")
%! assert( fromJSON('"Inf"'),"Inf")
%! assert( fromJSON('"1e308"'),"1e308")

%!test  ## all of the above in array form
%! assert( fromJSON('[1,1e308,Inf,-Infinity,NaN,nan,null]'),
%!         [1 Inf Inf -Inf NaN NaN,NaN])

%!test	##	empty array
%! assert( fromJSON('[]',true),[]);

%!test	##	empty cell array
%! assert( fromJSON('[]',false),{});

%!test	##	single element array
%! assert( fromJSON('[1]',true),1);
%! assert( fromJSON('[1]',false),{1});

%!test	##	JSON array to Octave vector
%! assert( fromJSON('[1,2,3,4]', true),
%!         [1 2 3 4])

%!test	##	JSON array to Octave cell array
%! assert( fromJSON('[1,2,3,4]', false),
%!         {1 2 3 4})

%!test	##	JSON nested array to Octave matrix
%! assert( fromJSON('[[1,2],[3,4]]', true),
%!         [1 2;3 4])

%!test	##	JSON nested array to nested Octave cell array
%! assert( fromJSON('[[1,2],[3,4]]', false),
%!         {{1 2},{3 4}})

%!test	##	JSON nested array to Octave ND array
%! assert( fromJSON('[[[1,3,5],[2,4,6]],[[7,9,11],[8,10,12]]]', true),
%!         reshape(1:12,2,3,2));

%!test	##	numerical ND cell array
%! assert( fromJSON('[[[1,3,5],[2,4,6]],[[7,9,11],[8,10,12]]]', false),
%!         {{{1,3,5},{2,4,6}},{{7,9,11},{8,10,12}}});

%!test	##	test default SARRAY
%! assert( fromJSON('[[1,2],[3,4]]'),
%!         fromJSON('[[1,2],[3,4]]',true))

%!test	##	bool array
%! assert( fromJSON('[true,false,false,true]'),
%!         !![1 0 0 1])

%!test	##	mixed bool and number array
%! assert( fromJSON("[[true,3],[false,1]]"),
%!         [1 3;0 1])

%!test	##	more N numerical ND array
%! json = "[[[[[1,3],[2,4]],[[5,7],[6,8]]],[[[11,13],[12,14]],[[15,17],[16,18]]]],[[[[21,23],[22,24]],[[25,27],[26,28]]],[[[31,33],[32,34]],[[35,37],[36,38]]]]]";
%! assert( fromJSON(json),
%!         reshape([1:8 11:18 21:28 31:38],2,2,2,2,2));

%!test	##	mismatch nested array (mix of scalar and cell array)
%! assert( fromJSON('[[1,2,3,4,5],[1,2]]', true),
%!         {[1 2 3 4 5] [1 2]})

%!test	##	more mismatched nested array
%! assert( fromJSON('[1,2,3,[2,3]]'),
%!         {1,2,3,[2,3]})

%!test	##	array of numerical array and mixed-class array
%! assert( fromJSON('[[1,2,3,"a"],[2,3,4,4]]', true),
%!         {{1 2 3 "a"},[2 3 4 4]})

%!test	##	mixed-class array
%! assert( fromJSON('[["a",2,3],[4,"b",5]]', true), 
%!         {'a' 2 3; 4 'b' 5});

%!test	##	mismatch nested array, safe parsing to cell array
%! assert( fromJSON('[[1,2,3,4,5],[1,2]]', false),
%!         {{1 2 3 4 5},{1 2}})

%!test	##	mixed-class array, safe parsing to cell array
%! assert( fromJSON('[["a",2,3],[4,"b",5]]', false), 
%!         {{'a' 2 3},{4 'b' 5}} );

%!test	##	more N numerical ND array
%! json = "[[[[[1,3],[2,4]],[[5,7],[6,8]]],[[[11,13],[12,14]],[[15,17],[16,18]]]],[[[[21,23],[22,24]],[[25,27],[26,28]]],[[[31,33],[32,34]],[[35,37],[36,38]]]]]";
%! json = regexprep(json,'(\d+)','"$1"'); % turn it input JSON array of strings
%! c    = cellfun(@num2str,num2cell(reshape([1:8 11:18 21:28 31:38],2,2,2,2,2)), 'uniformoutput', false);
%! assert( fromJSON(json),c);

%!test	##	JSON-like: with non-JSON, Octave, numerical notation (bonus feature)
%! assert( fromJSON('[Inf,-Inf,NaN,2i,pi,e]'),
%!         [Inf,-Inf,NaN,2*i,pi,e],1e-15);

%!test	##	beautified JSON array
%! assert( fromJSON("\n[\n\t  [  1, 2, 3,4],\n\t  [2,3,4,4]\n]    ",true),
%!         [[1 2 3 4];[2 3 4 4]])

%!test	##	beautified JSON array
%! assert( fromJSON("\n[\n\t  [  1, 2, 3,4],\n\t  [2,3,4,4]\n]    ",false),
%!         {{1,2,3,4},{2,3,4,4}})

%!test	##	incomplete array
%! warning('off','all');
%! assert( fromJSON("[1,2,3  "),
%!         [1,2,3]);

%!test	##	more incomplete array
%! warning('off','all');
%! assert( fromJSON("[[1,2,3],[3"),
%!         {[1,2,3],[3]});

%!test	##	string with whitespaces
%! assert( fromJSON("\"te\nss      df\t t\""),
%!         "te\nss      df\t t")

%!test	##	char array
%! assert( fromJSON('["a","b","c"]'),
%!         {'a','b','c'})

%!test	##	array of string
%! assert( fromJSON('["test","list","more"]'),
%!         {'test',"list","more"})

%!test	##	escaped quote
%! assert( fromJSON ('"tes\"t"'),
%!         'tes\"t');

%!test	##	escaped quote in array
%! assert( fromJSON('["te\"t","list","more"]'),
%!         {'te\"t' 'list' 'more'})

%!test	##	empty object to empty struct
%! assert( fromJSON('{}',true), struct())
%! assert( fromJSON('{}',false),struct())

%!test	##	struct to object
%! assert( fromJSON('{"a":3,"b":5}',true),
%!         struct("a",3,"b",5))

%!test	##	string with colons (guards against JSON object false postive)
%! assert( fromJSON('["2012-11-12T10:35:32Z","2012-11-13T08:35:12Z"]'), 
%!        {"2012-11-12T10:35:32Z", "2012-11-13T08:35:12Z"});

%!test	##	string with {} (guards against JSON object false postive)
%! assert( fromJSON('["some text {hello:3}","more text \"{hello:3}\""]'), 
%!        {'some text {hello:3}', 'more text \"{hello:3}\"'});

%!test	##	lazy JSON object
%! assert( fromJSON("{a:3,'b':5}",true), 
%!         struct("a",3,"b",5))

%!test	##	keys (quoted and unquoted) with whitespace
%! assert( fromJSON('{"a":3,"   key with space  ": 4,  more white space  : 40}'),
%!         struct("a",3,"   key with space  ",4,"more white space",40))

%!test	##	object with duplicate key
%! assert( fromJSON('{"a":3,"a":5}'),
%!         struct("a",5))

%!test	##	empty object key-val
%! assert( fromJSON('{a:3,,,    ,     b   :5}'),
%!         struct("a",3,"b",5))

%!test	##	object with object
%! assert( fromJSON('{a:{b:2}}', true), 
%!         struct('a',struct('b',2)));

%!test	##	object with object with object
%! assert( fromJSON('{a:{b:{c:3,d:4},e:5}}', true), 
%!         struct('a',struct('b',struct('c',3,'d',4),'e',5)));

%!test	##	object with array to struct with array
%! assert( fromJSON('{a:[1,2,3,4]}', true), 
%!         struct('a',[1 2 3 4]));

%!test	##	object with array to struct with cell array
%! assert( fromJSON('{a:[1,2,3,4]}', false), 
%!         struct('a',{{1 2 3 4}}));

%!test	##	array of objects to struct array
%! assert( fromJSON('[{a:1},{a:2},{a:3},{a:4}]',true),
%!         struct('a',{1 2 3 4}));

%!test	##	array of objects to cell array
%! assert( fromJSON('[{a:1},{a:2},{a:3},{a:4}]',false),
%!         num2cell(struct('a',{1 2 3 4})));

%!test	##	object with 2x2 array to struct with 2x2 array
%! assert( fromJSON('{a:[[1,3],[2,4]]}', true), 
%!         struct('a',[1 3;2 4]));

%!test	##	object with 2x2 array to struct with cell array
%! assert( fromJSON('{a:[[1,3],[2,4]]}', false), 
%!         struct('a',{{{1,3},{2,4}}}));

%!test	##	2x2 array of object to struct array
%! assert( fromJSON('[[{a:1},{a:2}],[{a:3},{a:4}]]',true), 
%!         struct('a',{1 2;3 4}));

%!test	##	2x2 array of object to cell array
%! assert( fromJSON('[[{a:1},{a:2}],[{a:3},{a:4}]]',false), 
%!         { num2cell(struct('a',{1 2})), num2cell(struct('a',{3 4}))} );

%!test	##	**nested** object with array to struct array
%! assert( fromJSON('{"a":{"b":[1,2,3]}}',true ), 
%!         struct("a",num2cell(struct("b",[1,2,3]))))  % <== struct array

%!test	##	**nested** object with array to cell array
%! assert( fromJSON('{"a":{"b":[1,2,3]}}',false), 
%!         struct("a",struct("b",{{1,2,3}})))            % <== struct with array b

%!test	##	object 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]))

%!test	##	object with number and string (guard against turing string into char array)
%! assert( fromJSON('{"a":1,"b":"hello"}',true), 
%!         struct("a",1,"b","hello"))

%!test	##	object with empty array  (gaurd against returning empty struct array)
%! assert( fromJSON('{"a":3,"b":[]}',true), 
%!         struct("a",3,"b",[]))

%!test	##	object with colon in key or value
%! assert( fromJSON('{"time": "10:35:00"}'), 
%!         struct("time", "10:35:00"));

%!test	##	array of structure with value with colons
%! assert( fromJSON('[{"time": "10:35"},{"time": "10:54"}]', true), 
%!         struct("time", {"10:35","10:54"}));

%!test	##	incomplete struct
%! warning('off','all');
%! assert( fromJSON('[[1,2,{a:3,b ],3,4,5]', true), 
%!         {{1,2,struct('a',3)},3,4,5});

%!test	##	struct with mixed class array SARRAY=true
%! assert( fromJSON('{"b":[1,2,{c:4}]}',true),
%!         struct('b',{{1,2,struct('c',4)}}));

%!test	##	struct with mixed class array SARRAY=false
%! assert( fromJSON('{"b":[1,2,{c:4}]}',false),
%!         struct('b',{{1,2,struct('c',4)}}));

%!test	##	2x2 array of struct
%! assert( fromJSON('[[{a:1},{a:3}],[{a:2},{a:4}]]'),
%!         struct('a',{1 3;2 4}));

%!test	##	ND array of struct to ND struct array
%! assert( fromJSON('[[[{a:1},{a:3}],[{a:2},{a:4}]],[[{a:11},{a:13}],[{a:12},{a:14}]]]'),
%!         struct('a',num2cell(reshape([1:4 11:14],2,2,2))));

%!test	##	mixed array with struct
%! assert( fromJSON('[2,{a:3,"b":5}]'),
%!         {2,struct("a",3,"b",5)})

%!test	##	more mixed array with struct
%! assert( fromJSON('[{a:3,"b":5},{a:3}]'),
%!         {struct("a",3,"b",5),struct("a",3)})

%!test	##	complex number object
%! assert( fromJSON('{"re":3,"im":5}'),     3+5i);
%! assert( fromJSON('{"real":3,"imag":5}'), 3+5i);

%!test	##	complex number lazy notation object
%! assert( fromJSON('{re:3,im:5}'), 3+5i);
%! assert( fromJSON('{real:3,imag:5}'), 3+5i);

%!test	##	fake complex number object
%! assert( fromJSON('{"re": "hello","im":5}'),
%!         struct('re',"hello",'im',5));

%!test	##	complex number in reverse order 
%! assert( fromJSON('{im:3,re:5}'), 5+3i);

%!test	##	complex with excess members
%! assert( fromJSON('{im:3,re:5,re:7,im:10}'),7+10i);

%!test	##	complex number object with mismatch re/im and real/imag
%! assert( fromJSON('{real:3,im:5}'),
%!         struct('real',3,'im',5));

%!test	##	apparent complex number object with extra member
%! assert( fromJSON('{re:3,im:5,t:3}'), 
%!         struct('re',3,'im',5,'t',3));

%!test	##	array of complex number object  to scalar array of complex number
%! assert( fromJSON('[{re:4,im:6},{re:5,im:7}]}',true),
%!         [4+6i,5+7i] );

%!test	##	array of complex number object  to cell array of complex number
%! assert( fromJSON('[{re:4,im:6},{re:5,im:7}]}',false), 
%!         {4+6i,5+7i} );

%!test	##	complex number object with array to scalar array of complex number
%! assert( fromJSON('{re:[4,5],im:[6,7]}',true), 
%!         [4+6i,5+7i] );

%!test	##	complex number object with array to struct with cell array
%! assert( fromJSON('{re:[4,5],im:[6,7]}',false), 
%!         struct("re",{{4,5}},"im",{{6,7}}));

%!test	##	complex number object with mismatch array sizes
%! assert( fromJSON('{re:[4,5],im:[6]}',true), 
%!         struct('re',[4,5],'im',6));

%!test	##	array complex number object
%! assert( fromJSON('[{re:4, im:6},{re:5,im:7}]'), 
%!         [4+6i,5+7i] );

%!test	##	complex number object with matrix
%! assert( fromJSON('[{re:[4,5;0 2],im:[6,7;3 0]}]'),
%!         [4+6i,5+7i;3i 2]);

%!test	##	array of complex number object
%! assert( fromJSON('[[{re:4,im:6},{re:1,im:0}],[{re:0,im:2}, {re:2,im:4}]]'),
%!         [4+6i 1;2i 2+4i]);

%!test	##	complex number object with ND array
%! assert( fromJSON('{re:[[[1,1],[2,2]],[[3,3],[4,4]]],im:[[[1,1],[2,2]],[[3,3],[4,4]]]}', true),
%!        reshape( (1+i)*[1 2 1 2 3 4 3 4], 2,2,2));

%!test  ##  ND array of complex number objects
%! assert( fromJSON('[[[{"re":1,"im":1},{"re":1,"im":1}],[{"re":2,"im":2},{"re":2,"im":2}]],[[{"re":3,"im":3},{"re":3,"im":3}],[{"re":4,"im":4},{"re":4,"im":4}]]]'),
%!        reshape( (1+i)*[1 2 1 2 3 4 3 4], 2,2,2));

%!test	##	array of objects with complex number member
%! assert( fromJSON('[{"a": {re:4, im:6}},{"a": {re:5,im:7}}]'), 
%!         struct('a',{4+6i,5+7i}) );

%!test	##	ND array objects with complex number members
%! obj = struct('a',{4+6i,5+7i;4+6i,5+7i});
%! obj(:,:,2) = obj;
%! assert( fromJSON('[[[{"a": {re:4, im:6}},{"a": {re:5,im:7}}],[{"a": {re:4, im:6}},{"a": {re:5,im:7}}]],[[{"a": {re:4, im:6}},{"a": {re:5,im:7}}],[{"a": {re:4, im:6}},{"a": {re:5,im:7}}]]]'), 
%!         obj );

%!test	##	array of objects with DEEP complex number member
%! assert( fromJSON('[{"a": {"b": {re:4, im:6}}},{"a": {re:5,im:7}}]'), 
%!         [struct('a',struct('b',4+6i)),struct('a',5+7i)] );

%!test	##	mismatch array (to cell array) of objects with DEEP complex number member
%! assert( fromJSON('[{"a":{"b":{re:4, im:6}}}, {"c":{"d":{"e":{re:5,im:7}}}}]'), 
%!         {struct('a',struct('b',4+6i)),struct('c',struct('d',struct('e',5+7i)))} );

%!test  # array of complex number in algebraic format (extracircular parsing)
%! assert( fromJSON('[[4+6i, 1+0i], [2i, 4i + 2]]'),
%!         [4+6i 1;2i 2+4i]);

%!test	##	complex number object mixed with number within array (test array parsing)
%! assert( fromJSON('[[4,{re:1,im:0}],[{re:0,im:2}, {re:4,im:6}]]'),
%!         [4 1;2i 4+6i]);

%!test	##	complex number object within a structure (nested)
%! assert( fromJSON('{"a": {re:1,im:5}}'),
%!         struct('a',1+5i));

%!test	##	complex number object with structure array (nested)
%! assert( fromJSON('[{"a":{re:1,im:5}},{"a":{re:2,im:5}}]'),
%!         struct('a',{1+5i,2+5i}));

%!test	##	complex number object deeply nested in object
%! assert( fromJSON('{a:1,b:{c:2,d:{e:3,f:{re:3,im:5}}}}'),
%!         struct('a',1,'b',struct('c',2,'d',struct('e',3,'f',3+5i))));

%!test  ## test octave inline fn  (convert to inline)
%! assert(fromJSON('"@@sin"'),@sin);
%! assert(func2str(fromJSON('"@@(x)3*x"')),func2str(@(x)3*x));

%!test  ## test apparent octave inline fn  (do NOT convert to inline)
%! assert(fromJSON('"@sin"'), '@sin');
%! assert(fromJSON('"@(x)3*x"'),'@(x)3*x');

%!test  ## test octave inline fn with SARRAY=false (do NOT convert to inline)
%! assert(fromJSON('"@@sin"', false), '@@sin');
%! assert(fromJSON('"@@(x)3*x"', false),'@@(x)3*x');

%!test	##	exotic object in structure
%! assert( fromJSON('{"a":"[java.math.BigDecimal]"}'),struct('a','[java.math.BigDecimal]'));

%!test	##	JSON with confusing '[]{},' AND missing quotes (hard string parse test)
%! warning('off','all');
%! obj=fromJSON('[{a:"tes, {}: [ ] t"},"lkj{} sdf",im mi{}ing quotes]');
%! assert(obj,{struct('a',"tes, {}: [ ] t"), 'lkj{} sdf','im mi{}ing quotes'})

%!test	##	garbage (non-quoted, meaningless string)
%! warning('off','all');
%! assert( fromJSON('garbage'),
%!         'garbage')

%!test	##	garbage in array
%! warning('off','all');
%! assert( fromJSON('[1,garbage]'),
%!         {1,'garbage'})

%!test	##	garbage in struct
%! warning('off','all');
%! assert( fromJSON('{a:garbage}'),
%!         struct('a','garbage'))

%!test	##	exotic object (placeholder of class name)
%! assert( fromJSON('"[java.math.BigDecimal]"'),
%!         '[java.math.BigDecimal]');

%!test	##	beautified JSON object (extraneous whitespace/newline)
%! assert( fromJSON("\n  {  \n\t\"a\"\t\n\n\n  :\t\n\n\n  3\n\t} \n \n"), 
%!         struct("a",3))

%!test	##	beautified JSON string with array (test parse with extraneous whitespace)
%! pretty_json ="{\n  \"data\": [\n    {\n      \"vendor\": \"0x10de\",\n      \"details\": {\n        \"created\": \"2012-11-12T10:35:32Z\"\n      },\n      \"_status\": \"synced\"\n    }\n  ]\n}";
%!
%! obj = struct('data', ...
%!          struct('vendor','0x10de', ...
%!                 'details', struct('created','2012-11-12T10:35:32Z'), ...
%!                 '_status', 'synced'
%!           )
%!       );
%!                      
%! assert( fromJSON(pretty_json),obj,true);


%!test  ## jsondecode's Arrays with the same field names in the same order.
%! json = ["[\n", ...
%!   "{\n", ...
%!     '"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"', ...
%!       '}', ...
%!     ']', ...
%!   '},', ...
%!   "{\n", ...
%!     '"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"', ...
%!       '}', ...
%!     ']', ...
%!   "}\n\n,", ...
%!   "{\n", ...
%!     '"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"', ...
%!       '}', ...
%!     ']', ...
%!   '}', ...
%! ']'];
%!
%! f1 = num2cell(struct ('id', {0 1 2}, 'name', {'Collins' 'Hays' 'Griffin'}));
%! f2 = num2cell(struct ('id', {0 1 2}, 'name', {'Osborn' 'Mcdowell' 'Jewel'}));
%! f3 = num2cell(struct ('id', {0 1 2}, 'name', {'Socorro' 'Darla' 'Leanne'}));
%!   
%! keys = {'x_id','index','guid','latitude','longitude','friends'};
%! val1 = {'5ee28980fc9ab3', 0, 'b229d1de-f94a', -17.124067, -61.161831, f1};
%! val2 = {'5ee28980dd7250', 1, '39cee338-01fb', 13.205994,  -37.276231, f2};
%! val3 = {'5ee289802422ac', 2, '3db8d55a-663e', -35.453456,  14.080287, f3};
%! 
%! exp = cell2struct({val1{};val2{};val3{}}',keys)';
%! exp = num2cell(exp);
%! assert (fromJSON(json, false), exp);

%!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

