1## Copyright (C) 2019-2021 Ketan M. Patel 2## 3## This program is free software: you can redistribute it and/or modify it 4## under the terms of the GNU General Public License as published by 5## the Free Software Foundation, either version 3 of the License, or 6## (at your option) any later version. 7## 8## This program is distributed in the hope that it will be useful, but 9## WITHOUT ANY WARRANTY; without even the implied warranty of 10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11## GNU General Public License for more details. 12## 13## You should have received a copy of the GNU General Public License 14## along with this program. If not, see 15## <https://www.gnu.org/licenses/>. 16 17## -*- texinfo -*- 18## @deftypefn {} {@var{obj} =} fromJSON (@var{str}) 19## @deftypefnx {} {@var{obj} =} fromJSON (@var{str}, @var{sarray}) 20## Convert a JSON string into a native Octave object. 21## 22## fromJSON especially strives to convert numerical JSON arrays into Octave 23## vector, matrix or ND array. Special provisions are made to recognize 24## +/-Inf, NaN and complex numbers, which are conventionally not permited in 25## JSON strings. 26## 27## Input arguments: 28## 29## @itemize 30## @item 31## @var{str} is a JSON string. 32## @end item 33## 34## @item 35## @var{sarray}, (default TRUE) logical value that determines how JSON member 36## arrays are parsed. Setting it to FALSE is recommended for safer parsing of 37## non-numerical, mixed-class or mixed-size arrays JSON payloads. 38## 39## Leave @var{sarray} set to TRUE for fast parsing of numerical JSON arrays and 40## objects. Octave vectors, matrices, ND arrays and struct arrays are returned 41## as much as possible, otherwise returned as a combination of vectors/matrices 42## and cell arrays. 43## 44## Set @var{sarray} to FALSE if, with fast parsing, output does not match 45## expections, particularly if @var{str} mainly comprises JSON objects/arrays 46## with strings. All JSON member arrays are returned as (nested) Octave cell 47## arrays. 48## @end item 49## @end itemize 50## 51## Output: 52## 53## @itemize 54## @item 55## @var{obj} is a native Octave object. JSON number, logical, array, and 56## object strings are converted to Octave numbers, logicals, vectors and 57## structs, respectively. Quoted or unrecognizable JSON fragments are returned 58## as NaN values. 59## @end itemize 60## 61## Special numbers: 62## 63## The specification for JSON does not allow +/-Inf, NaN or complex numbers; 64## nevertheless, provisions are made here to enable these important numbers: 65## 66## @itemize 67## @item 68## JSON number strings '+/-1e308' are rounded to +/-Inf. 69## @end item 70## 71## @item 72## Unquoted JSON string 'null' is converted to NaN. 73## @end item 74## 75## @item 76## JSON objects, or arrays thereof, with exclusive members "real" and "imag" 77## (or "re" and "im") will be converted to Octave complex numbers, vectors or 78## matrices, respectively. 79## 80## @example 81## @w{e.g. '@{"re":3,"im":1@}' => 3 + 1i } 82## @w{ '[1,@{"re":3,"im":1@}]' => [ 1 + 0i, 3 + 1i ] } 83## @end example 84## @end item 85## @end itemize 86## 87## @seealso{toJSON} 88## @end deftypefn 89 90## Author: Ketan M. Patel <kmpatel@roc-photonics.com> 91## Created: 2019-06-01 92 93function [obj] = fromJSON ( str=' ', SARRAY=[] ); 94 95 if ~( isnumeric(SARRAY) || isbool(SARRAY) ); 96 _warn_("invalid SARRAY"); 97 SARRAY = []; 98 endif 99 100 ## set defaults 101 ischar(str) && str || (str=' '); 102 ~isempty(SARRAY) || (SARRAY=true); 103 104 str(1) > 126 && (str = str( find(str<127,1) : end )); # drop leading encoding chars 105 106 wstate = warning('off',"Octave:num-to-str"); 107 obj = _fromjson_(str,SARRAY); 108 warning(wstate); 109 110endfunction 111 112function [obj, remain] = _fromjson_ ( str, SARRAY ); 113 114 [btag, block, remain] = _get_block_(str); 115 116 switch( btag ); 117 case '['; obj = _to_array_ (block, SARRAY); 118 case '{'; obj = _to_struct_ (block, SARRAY); 119 case '"'; obj = _to_string_ (block, SARRAY); 120 otherwise; obj = _to_number_ (block, SARRAY); 121 endswitch 122 123 remain = regexprep(remain,'^[\s,]*',''); #!!! keep this HERE, not in _get_block_ 124 125endfunction 126 127## gets block quoted-string/array/object block from string 128## this will work with single-quoted strings, but only double-quoted strings are safe 129function [btag, block, remain] = _get_block_ (str); 130 131 [open, btag] = regexp(str,'[^\s,]','once','start','match'); 132 133 switch( btag ); # find ALL bracket tag (open/close) positions 134 135 case '['; idx = [strfind(str,'[') -strfind(str,']')]; # strfind is 100x faster than regexp 136 case '{'; idx = [strfind(str,'{') -strfind(str,'}')]; 137 138 case {'"',"'"}; idx = -strfind(str,btag); idx(1) *= -1; # all but first are closers 139 btag = '"'; # regard single-quote as double-quote (lazy JSON) 140 141 otherwise; [block,remain] = strtok(str(open:end),','); 142 return; 143 endswitch 144 145 idx( '\' == str([1 abs(idx(2:end))-1]) ) = []; # exclude escaped-tags (e.g.'\[', '\{', '\"', etc) 146 147 ## filter out double-quoted (exclusively) block tags 148 ( btag ~= '"' && (q = strfind(str,'"') ) # find all double-quote marks 149 && (q = q(str(q-1) ~= '\')) > 1 # discard \", stop if only single " remains 150 && (idx = _bfilter_(idx, q)) ); # filter out quoted btags 151 152 if ~( numel(idx) > 1 && ( close=-idx(2) ) > 0 ); # unless simple (non-nested) block... 153 154 idx = idx([~,i] = sort(abs(idx))); # sort idx by abs(idx) 155 156 if( ( ci=find(0 == cumsum(sign(idx)), 1) ) ); 157 close = -idx(ci); 158 else 159 close = numel(str)+1; 160 _warn_(btag,str); 161 endif 162 end 163 164 block = str( open+1 : close-1 ); 165 remain = str( close+1 : end ); 166 167endfunction 168 169function [arr] = _to_array_ ( str, SARRAY ); 170 171 ok = SARRAY && ~( regexp(str,'\[\s*\[','once') # no ND array 172 || regexp(str,':\s*[''"]','once') # no struct array with strings 173 || strfind(str,'=') ); # no apparent assignment op 174 if( ok ); 175 arr = regexprep( _rep_(str, Inf), ',(?=\s*\[)',';'); # JSON to Octave array syntax 176 177 if( strfind(str,'{') ); # JSON object 178 arr = [ _lazyJSON_ "; arr=_deep_complex_([" _rep_(arr, struct) "])" ]; 179 else # JSON numerical? array 180 arr = [ "arr=[" arr "]" ]; 181 endif 182 183 eval([ arr "; ok = isnumeric(arr) || isstruct(arr);" ], "ok=false;"); 184 185 endif 186 187 if( ~ok ); # this can be slow, avoid getting here if possible 188 189 arr = {}; 190 191 while( str ); # grind thru string, one array element at a time 192 [arr{end+1} str] = _fromjson_(str,SARRAY); 193 endwhile; 194 195 if( SARRAY ); 196 switch numel(arr); 197 case 0; arr = []; 198 case 1; arr = arr{1}; 199 otherwise; arr = _ndarray_(arr); 200 endswitch 201 endif 202 203 endif 204 205endfunction 206 207function [obj] = _to_struct_ ( str, SARRAY ); 208 209 obj = struct(); 210 211 ## we're making this harder than needed in order to permit lazy JSON 212 ## i.e. unquoted and single quote keys (but! start/end quote chars must match) 213 214 while( ( q=regexp(str, '[^\s,:]', 'once','match') ) # detect key quote-char or empty str 215 && ( m=regexp(str, _qrgx_(q), 'once','names') ).k ); # get key,val or fail 216 217 [obj.(m.k) str] = _fromjson_(m.v, SARRAY); 218 219 endwhile 220 221 regexp(str,'[^\s]','once') && _warn_('}', str); 222 obj = _complex_( obj ); # _deep_complex_ not necessary here 223 224endfunction 225 226function [str] = _to_string_ ( str, SARRAY ); 227 228 if( SARRAY && (numel(str) > 2 && str(1:2) == '@') ); 229 try str = str2func(str(2:end)); 230 catch _warn_('@', str); 231 end_try_catch; 232 endif 233 234endfunction 235 236function [num] = _to_number_( str, SARRAY ); 237 238 if( strfind(str,'=') ); # guard against assignment expr 239 num = 'error()'; 240 else 241 num = ['num=[' _rep_(str,Inf) '];']; 242 endif 243 244 eval(num, ['_warn_("invalid frag",str); num=NaN;']); 245 246endfunction 247 248##========================= helpers ======================= 249 250## filter out quoted block tags 251function [idx] = _bfilter_(idx,q); 252 253 qo = q(1:2:end-1)'; # open quote (ignore unclosed quote) 254 qc = q(2:2:end)'; # close quote 255 256 ai = abs(idx); 257 idx( any(qo < ai & ai < qc,1) ) = []; # purge quoted block tags 258 259endfunction 260 261## replace string frags according to set pattern 262function [str] = _rep_ ( str, pat ); 263 264 if isstruct(pat); 265 pat = {'{','struct(',':',',','}',')'}; 266 else 267 pat = {'null','NaN ','e308','e999','Infinity','Inf ',"\n",' '}; # keep empty spaces 268 endif 269 270 do 271 str = strrep(str,pat{1:2},'overlaps',false); 272 pat(1:2) = []; 273 until isempty(pat); 274 275endfunction 276 277## try to make ND array (either mat or cell) 278function [c] = _ndarray_ ( c ); 279 280 sz = size(e = c{1}); t = class(e); 281 282 if ~( ~ischar(e) && sz && cellfun(@(v)isa(v,t)&&size(v)==sz, c) ); 283 return; 284 elseif( isscalar(e) ); # try matrix conversion, ok to fail if not uniform class 285 try c = [c{:}]; end; 286 elseif( isvector(e) ); # cell array of mat or cell array 287 c = reshape([c{:}], numel(e), numel(c)).'; 288 else # make ND array 289 index.type = "()"; 290 index.subs = repmat({':'},1,ndims(e)+1); 291 i = numel(c); 292 293 do 294 index.subs{end} = i; 295 e = subsasgn(e,index,c{i}); 296 until (--i) == 1; 297 c = e; 298 endif 299 300endfunction 301 302## convert complex number struct DEEP WITHIN structure to complex number 303function [s] = _deep_complex_(s); 304 305 if( isstruct(s) && isstruct( s=_complex_(s) ) ); 306 vals = cellfun(@_deep_complex_, struct2cell(s), 'uniformoutput', false); 307 s = cell2struct(vals, __fieldnames__(s)); 308 endif 309 310endfunction 311 312## convert complex number struct to complex number 313function [s] = _complex_ ( s ); 314 315 if( numel( keys=__fieldnames__(s) ) == 2 316 && ( ismember( k={'real','imag'}, keys) || ismember( k={'re','im'}, keys) ) 317 && isnumeric( re=[s.(k{1})] ) 318 && isnumeric( im=[s.(k{2})] ) 319 && size_equal(re,im) ); 320 321 s = reshape( complex(re,im), ifelse(isscalar(s), size(re), size(s)) ); 322 323 endif 324 325endfunction 326 327## for max conversion speeed, gaurd against common lazy JSON (unquoted keys) 328function [str] = _lazyJSON_ ( ); 329 330 str = 'x="x";y="y";z="z";real=re="re";imag=im="im";r="r";rho="rho";theta="theta";phi="phi";'; 331 332endfunction 333 334## compute regexp pattern for JSON object key (quoted or unquoted) 335function [rgx] = _qrgx_(q); 336 337 sum(q=="\"'") || (q=''); 338 rgx = ['[\s,]*' q '(?<k>.+?)' q '\s*:\s*(?<v>[^\s].*)' ]; 339 340endfunction 341 342 343function [out] = _warn_ ( msg, frag='' ); 344 345 if( numel(msg) == 1 ); 346 switch(msg); 347 case '{'; msg = "unclosed object"; 348 case '['; msg = "unclosed array"; 349 case '"'; msg = "unclosed quote"; 350 case '}'; msg = "malformed structure"; 351 regexp(lastwarn,'unclosed') || ( frag=[frag '}'] ); 352 case '@'; msg = "invalid inline func string"; 353 endswitch 354 endif 355 356 frag && ( frag=["`" frag "`"] ); 357 numel(frag)>40 && (frag = [frag(1:27) ' ... ' frag(end-27:end)]); 358 out = warning("fromJSON: %s %s\n",msg,frag); 359 360endfunction 361 362 363################################################################################ 364############################### BIST ####################################### 365################################################################################ 366 367%!test ## input validation 368%! assert( fromJSON(),[]); % ok, reference 369%!warning <invalid SARRAY> fromJSON('',struct); 370%! 371 372%!test ## invalid input validation 373%! bad = {4,{},@sin,struct(),'',false,0,[1,2,3]}; 374%! assert(fromJSON(4), []); 375%! assert(fromJSON({}), []); 376%! assert(fromJSON(@sin), []); 377%! assert(fromJSON(struct), []); 378%! assert(fromJSON(false), []); 379%! assert(fromJSON([1,2,3]),[]); 380 381############### JSON number BIST ##################### 382 383%!test ## number 384%! assert( fromJSON('4'),4) 385 386%!test ## number string 387%! assert( fromJSON('"string"'),"string") 388 389%!test ## bool 390%! assert( fromJSON('true'),true) 391 392%!test ## round up to Inf 393%! assert( fromJSON('1e308'), Inf) 394%! assert( fromJSON('-1e308'), -Inf) 395 396%!test ## do NOT round up to Inf 397%! assert( fromJSON('1e307'), 1e307) 398%! assert( fromJSON('-1e307'), -1e307) 399 400%!test ## automatically parse as Inf 401%! assert( fromJSON('1e309'), Inf) 402%! assert( fromJSON('-1e309') -Inf) 403%! assert( fromJSON('1e999'), Inf) 404%! assert( fromJSON('-1e999'), -Inf) 405 406%!test ## null 407%! assert( fromJSON( 'null' ),NaN) 408 409%!test ## in case JSON spec commitee gets head out of ass 410%! assert( fromJSON('Inf'), Inf) 411%! assert( fromJSON('-Inf'), -Inf) 412%! assert( fromJSON('Infinity'), Inf) 413%! assert( fromJSON('-Infinity'), -Inf) 414%! assert( fromJSON('NaN'), NaN) 415%! assert( fromJSON('nan'), NaN) 416 417%!test ## quoted null,Inf,etc (guard against false-positive, leave alone) 418%! assert( fromJSON('"null"'),"null") 419%! assert( fromJSON('"Inf"'),"Inf") 420%! assert( fromJSON('"1e308"'),"1e308") 421 422%!test ## all of the above in array form 423%! assert( fromJSON('[1,1e308,Inf,-Infinity,NaN,nan,null]'), 424%! [1 Inf Inf -Inf NaN NaN,NaN]) 425 426%!test ## garbage (non-quoted, meaningless string) 427%!warning <invalid> assert( fromJSON('garbage'), NaN) 428 429############### JSON aray BIST ##################### 430 431%!test ## empty array 432%! assert( fromJSON('[]',true),[]); 433 434%!test ## empty cell array 435%! assert( fromJSON('[]',false),{}); 436 437%!test ## single element array 438%! assert( fromJSON('[1]',true),1); 439%! assert( fromJSON('[1]',false),{1}); 440 441%!test ## JSON array to Octave vector 442%! assert( fromJSON('[1,2,3,4]', true), 443%! [1 2 3 4]) 444 445%!test ## JSON array to Octave vector 446%! assert( fromJSON('[[1],[2],[3],[4]]', true), 447%! [1;2;3;4]) 448 449%!test ## JSON array to Octave cell array 450%! assert( fromJSON('[1,2,3,4]', false), 451%! {1 2 3 4}) 452 453%!test ## JSON array to Octave vector 454%! assert( fromJSON('[[1],[2],[3],[4]]', false), 455%! {{1},{2},{3},{4}}) 456 457%!test ## JSON nested array to Octave matrix 458%! assert( fromJSON('[[1,2],[3,4]]', true), 459%! [1 2;3 4]) 460 461%!test ## JSON nested array to nested Octave cell array 462%! assert( fromJSON('[[1,2],[3,4]]', false), 463%! {{1 2},{3 4}}) 464 465%!test ## JSON nested array to Octave ND array 466%! assert( fromJSON('[[[1,3,5],[2,4,6]],[[7,9,11],[8,10,12]]]', true), 467%! reshape(1:12,2,3,2)); 468 469%!test ## numerical ND cell array 470%! assert( fromJSON('[[[1,3,5],[2,4,6]],[[7,9,11],[8,10,12]]]', false), 471%! {{{1,3,5},{2,4,6}},{{7,9,11},{8,10,12}}}); 472 473%!test ## test default SARRAY 474%! assert( fromJSON('[[1,2],[3,4]]'), 475%! fromJSON('[[1,2],[3,4]]',true)) 476 477%!test ## bool array 478%! assert( fromJSON('[true,false,false,true]'), 479%! !![1 0 0 1]) 480 481%!test ## mixed bool and number array 482%! assert( fromJSON("[[true,3],[false,1]]"), 483%! [1 3;0 1]) 484 485%!test ## more N numerical ND array 486%! 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]]]]]"; 487%! assert( fromJSON(json), 488%! reshape([1:8 11:18 21:28 31:38],2,2,2,2,2)); 489 490%!test ## mismatch nested array (mix of scalar and cell array) 491%! assert( fromJSON('[[1,2,3,4,5],[1,2]]', true), 492%! {[1 2 3 4 5] [1 2]}) 493 494%!test ## more mismatched nested array 495%! assert( fromJSON('[1,2,3,[2,3]]'), 496%! {1,2,3,[2,3]}) 497 498%!test ## array of numerical array and mixed-class array 499%! assert( fromJSON('[[1,2,3,"a"],[2,3,4,4]]', true), 500%! {{1 2 3 "a"},[2 3 4 4]}) 501 502%!test ## mixed-class array 503%! assert( fromJSON('[["a",2,3],[4,"b",5]]', true), 504%! {'a' 2 3; 4 'b' 5}); 505 506%!test ## mismatch nested array, safe parsing to cell array 507%! assert( fromJSON('[[1,2,3,4,5],[1,2]]', false), 508%! {{1 2 3 4 5},{1 2}}) 509 510%!test ## mixed-class array, safe parsing to cell array 511%! assert( fromJSON('[["a",2,3],[4,"b",5]]', false), 512%! {{'a' 2 3},{4 'b' 5}} ); 513 514%!test ## more N numerical ND array 515%! 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]]]]]"; 516%! json = regexprep(json,'(\d+)','"$1"'); % turn it input JSON array of strings 517%! c = cellfun(@num2str,num2cell(reshape([1:8 11:18 21:28 31:38],2,2,2,2,2)), 'uniformoutput', false); 518%! assert( fromJSON(json),c); 519 520%!test ## JSON-like: with non-JSON, Octave, numerical notation (bonus feature) 521%! assert( fromJSON('[Inf,-Inf,NaN,2i,pi,e]'), 522%! [Inf,-Inf,NaN,2*i,pi,e],1e-15); 523 524 525%!test ## beautified JSON vector 526%! assert( fromJSON("\n[1,\n2\n]\n",true), 527%! [1,2]) 528 529%! assert( fromJSON("\n[1,\n2\n]\n",false), 530%! {1,2}) 531 532%!test ## beautified JSON array 533%! assert( fromJSON("[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ]\n]",true), 534%! [[1 2];[3 4]]) 535 536%!test ## beautified JSON array 537%! assert( fromJSON("[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ]\n]",false), 538%! {{1,2},{3,4}}) 539 540%!test ## incomplete array 541%! warning('off','all'); 542%! assert( fromJSON("[1,2,3 "), 543%! [1,2,3]); 544 545%!test ## more incomplete array 546%! warning('off','all'); 547%! assert( fromJSON("[[1,2,3],[3"), 548%! {[1,2,3],[3]}); 549 550%!test ## string with whitespaces 551%! assert( fromJSON("\"te\nss df\t t\""), 552%! "te\nss df\t t") 553 554%!test ## char array 555%! assert( fromJSON('["a","b","c"]'), 556%! {'a','b','c'}) 557 558%!test ## array of string 559%! assert( fromJSON('["test","list","more"]'), 560%! {'test',"list","more"}) 561 562%!test ## escaped quote 563%! assert( fromJSON ('"tes\"t"'), 564%! 'tes\"t'); 565 566%!test ## escaped quote in array 567%! assert( fromJSON('["te\"t","list","more"]'), 568%! {'te\"t' 'list' 'more'}) 569 570%!test ## garbage in array 571%! warning('off','all'); 572%! assert( fromJSON('[1,garbage]'), [1,NaN]) 573 574############### JSON object BIST ##################### 575 576%!test ## empty object to empty struct 577%! assert( fromJSON('{}',true), struct()) 578%! assert( fromJSON('{}',false),struct()) 579%! assert( fromJSON('{ }',false),struct()) 580 581%!test ## struct to object 582%! assert( fromJSON('{"a":3,"b":5}',true), 583%! struct("a",3,"b",5)) 584 585%!test ## unclosed object 586%!warning <unclosed> assert(fromJSON('{a:3,b:4'),struct("a",3,"b",4)); 587 588%!test ## string with colons (guards against JSON object false postive) 589%! assert( fromJSON('["2012-11-12T10:35:32Z","2012-11-13T08:35:12Z"]'), 590%! {"2012-11-12T10:35:32Z", "2012-11-13T08:35:12Z"}); 591 592%!test ## string with {} (guards against JSON object false postive) 593%! assert( fromJSON('["some text {hello:3}","more text \"{hello:3}\""]'), 594%! {'some text {hello:3}', 'more text \"{hello:3}\"'}); 595 596%!test ## lazy JSON object (unquoted and single-quote keys) 597%! assert( fromJSON("{a:3,'b':5}",true), 598%! struct("a",3,"b",5)) 599 600%!test ## bad (but passable) lazy JSON object, unquoted-key with quote char at end 601%! assert( fromJSON("{a':3,b\":5}",true), 602%! struct("a'",3,'b"',5)) 603 604%!test ## bad (failing) JSON object (mismatched or unclosed quoted-keys) 605%!warning <malformed> assert( fromJSON("{'a :3}"), struct()); 606%!warning <malformed> assert( fromJSON("{'a\":3}"), struct()); 607%!warning <malformed> assert( fromJSON("{\"a :3}"), struct()); 608%!warning <malformed> assert( fromJSON("{\"a':3}"), struct()); 609 610%!test ## keys (quoted and unquoted) with whitespace 611%! assert( fromJSON('{"a":3," key with space ": 4, more white space : 40}'), 612%! struct("a",3," key with space ",4,"more white space",40)) 613 614%!test ## object with duplicate key 615%! assert( fromJSON('{"a":3,"a":5}'), 616%! struct("a",5)) 617 618%!test ## empty object key-val 619%! assert( fromJSON('{a:3,,, , b :5}'), 620%! struct("a",3,"b",5)) 621 622%!test ## object with object 623%! assert( fromJSON('{a:{b:2}}', true), 624%! struct('a',struct('b',2))); 625 626%!test ## object with object with object 627%! assert( fromJSON('{a:{b:{c:3,d:4},e:5}}', true), 628%! struct('a',struct('b',struct('c',3,'d',4),'e',5))); 629 630%!test ## object with array to struct with array 631%! assert( fromJSON('{a:[1,2,3,4]}', true), 632%! struct('a',[1 2 3 4])); 633 634%!test ## object with array to struct with cell array 635%! assert( fromJSON('{a:[1,2,3,4]}', false), 636%! struct('a',{{1 2 3 4}})); 637 638%!test ## array of objects to struct array 639%! assert( fromJSON('[{a:1},{a:2},{a:3},{a:4}]',true), 640%! struct('a',{1 2 3 4})); 641 642%!test ## array of objects to cell array 643%! assert( fromJSON('[{a:1},{a:2},{a:3},{a:4}]',false), 644%! num2cell(struct('a',{1 2 3 4}))); 645 646%!test ## object with 2x2 array to struct with 2x2 array 647%! assert( fromJSON('{a:[[1,3],[2,4]]}', true), 648%! struct('a',[1 3;2 4])); 649 650%!test ## object with 2x2 array to struct with cell array 651%! assert( fromJSON('{a:[[1,3],[2,4]]}', false), 652%! struct('a',{{{1,3},{2,4}}})); 653 654%!test ## 2x2 array of object to struct array 655%! assert( fromJSON('[[{a:1},{a:2}],[{a:3},{a:4}]]',true), 656%! struct('a',{1 2;3 4})); 657 658%!test ## 2x2 array of object to cell array 659%! assert( fromJSON('[[{a:1},{a:2}],[{a:3},{a:4}]]',false), 660%! { num2cell(struct('a',{1 2})), num2cell(struct('a',{3 4}))} ); 661 662%!test ## **nested** object with array to struct array 663%! assert( fromJSON('{"a":{"b":[1,2,3]}}',true ), 664%! struct("a",num2cell(struct("b",[1,2,3])))) % <== struct array 665 666%!test ## **nested** object with array to cell array 667%! assert( fromJSON('{"a":{"b":[1,2,3]}}',false), 668%! struct("a",struct("b",{{1,2,3}}))) % <== struct with array b 669 670%!test ## object with mixed-size arrays (will not honor SARRAY=true) 671%! assert( fromJSON('{"a":[1,2],"b":[3,4,5]}',true), 672%! struct("a",[1,2],"b",[3,4,5])) 673 674%!test ## object with number and string (guard against turing string into char array) 675%! assert( fromJSON('{"a":1,"b":"hello"}',true), 676%! struct("a",1,"b","hello")) 677 678%!test ## object with empty array (gaurd against returning empty struct array) 679%! assert( fromJSON('{"a":3,"b":[]}',true), 680%! struct("a",3,"b",[])) 681 682%!test ## object with colon value 683%! assert( fromJSON('{"time": "10:35:00"}'), 684%! struct("time", "10:35:00")); 685 686%!test ## object with colon key 687%! assert( fromJSON('{"ti:me": "10:35:00", ":tricky:" : 4}'), 688%! struct("ti:me", "10:35:00",":tricky:",4)); 689 690%!test ## array of structure with value with colons 691%! assert( fromJSON('[{"time": "10:35"},{"time": "10:54"}]', true), 692%! struct("time", {"10:35","10:54"})); 693 694%!test ## array of structure with value with keys 695%! assert( fromJSON('[{"ti:me": "10:35"},{"ti:me": "10:54"}]', true), 696%! struct("ti:me", {"10:35","10:54"})); 697 698%!test ## incomplete struct 699%!warning <malformed> assert( fromJSON('{a:3,b }', true), 700%! struct('a',3)); 701 702%!test ## incomplete struct 703%!warning <malformed> assert( fromJSON('{a:3,b: }', true), 704%! struct('a',3)); 705 706 707%!test ## struct with mixed class array SARRAY=true 708%! assert( fromJSON('{"b":[1,2,{c:4}]}',true), 709%! struct('b',{{1,2,struct('c',4)}})); 710 711%!test ## struct with mixed class array SARRAY=false 712%! assert( fromJSON('{"b":[1,2,{c:4}]}',false), 713%! struct('b',{{1,2,struct('c',4)}})); 714 715%!test ## 2x2 array of struct 716%! assert( fromJSON('[[{a:1},{a:3}],[{a:2},{a:4}]]'), 717%! struct('a',{1 3;2 4})); 718 719%!test ## ND array of struct to ND struct array 720%! assert( fromJSON('[[[{a:1},{a:3}],[{a:2},{a:4}]],[[{a:11},{a:13}],[{a:12},{a:14}]]]'), 721%! struct('a',num2cell(reshape([1:4 11:14],2,2,2)))); 722 723%!test ## mixed array with struct 724%! assert( fromJSON('[2,{a:3,"b":5}]'), 725%! {2,struct("a",3,"b",5)}) 726 727%!test ## more mixed array with struct 728%! assert( fromJSON('[{a:3,"b":5},{a:3}]'), 729%! {struct("a",3,"b",5),struct("a",3)}) 730 731%!test ## garbage in struct 732%! warning('off','all'); 733%! assert( fromJSON('{a:garbage}'), struct('a', NaN)); 734 735######### complex number JSON object BIST ################ 736 737%!test ## complex number object 738%! assert( fromJSON('{"re":3,"im":5}'), 3+5i); 739%! assert( fromJSON('{"real":3,"imag":5}'), 3+5i); 740 741%!test ## complex number lazy notation object 742%! assert( fromJSON('{re:3,im:5}'), 3+5i); 743%! assert( fromJSON('{real:3,imag:5}'), 3+5i); 744 745%!test ## fake complex number object 746%! assert( fromJSON('{"re": "hello","im":5}'), 747%! struct('re',"hello",'im',5)); 748 749%!test ## complex number in reverse order 750%! assert( fromJSON('{im:3,re:5}'), 5+3i); 751 752%!test ## complex with excess members 753%! assert( fromJSON('{im:3,re:5,re:7,im:10}'),7+10i); 754 755%!test ## complex number object with mismatch re/im and real/imag 756%! assert( fromJSON('{real:3,im:5}'), 757%! struct('real',3,'im',5)); 758 759%!test ## apparent complex number object with extra member 760%! assert( fromJSON('{re:3,im:5,t:3}'), 761%! struct('re',3,'im',5,'t',3)); 762 763%!test ## array of complex number object to scalar array of complex number 764%! assert( fromJSON('[{re:4,im:6},{re:5,im:7}]}',true), 765%! [4+6i,5+7i] ); 766 767%!test ## array of complex number object to cell array of complex number 768%! assert( fromJSON('[{re:4,im:6},{re:5,im:7}]}',false), 769%! {4+6i,5+7i} ); 770 771%!test ## complex number object with array to scalar array of complex number 772%! assert( fromJSON('{re:[4,5],im:[6,7]}',true), 773%! [4+6i,5+7i] ); 774 775%!test ## complex number object with array to struct with cell array 776%! assert( fromJSON('{re:[4,5],im:[6,7]}',false), 777%! struct("re",{{4,5}},"im",{{6,7}})); 778 779%!test ## complex number object with mismatch array sizes 780%! assert( fromJSON('{re:[4,5],im:[6]}',true), 781%! struct('re',[4,5],'im',6)); 782 783%!test ## complex number object with mismatch array sizes 784%! assert( fromJSON('{re:[4,5],im:[6]}',true), 785%! struct('re',[4,5],'im',6)); 786 787%!test ## bogus complex number object with mixed classed array 788%! assert( fromJSON('{re:[4,5],im:[6,"a"]}',true), 789%! struct('re',[4,5],'im',{{6,'a'}})); 790 791%!test ## array of bogus complex number object 792%! assert( fromJSON('[{re:4,im:6},{re:5,im:"a"}]',true), 793%! {4+6i,struct('re',5,'im','a')}); 794 795%!test ## complex number object with matrix 796%! assert( fromJSON('[{re:[4,5;0 2],im:[6,7;3 0]}]'), 797%! [4+6i,5+7i;3i 2]); 798 799%!test ## array of complex number object 800%! assert( fromJSON('[[{re:4,im:6},{re:1,im:0}],[{re:0,im:2}, {re:2,im:4}]]'), 801%! [4+6i 1;2i 2+4i]); 802 803%!test ## complex number object with ND array 804%! assert( fromJSON('{re:[[[1,1],[2,2]],[[3,3],[4,4]]],im:[[[1,1],[2,2]],[[3,3],[4,4]]]}', true), 805%! reshape( (1+i)*[1 2 1 2 3 4 3 4], 2,2,2)); 806 807%!test ## ND array of complex number objects 808%! 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}]]]'), 809%! reshape( (1+i)*[1 2 1 2 3 4 3 4], 2,2,2)); 810 811%!test ## array of objects with complex number member 812%! assert( fromJSON('[{"a": {re:4, im:6}},{"a": {re:5,im:7}}]'), 813%! struct('a',{4+6i,5+7i}) ); 814 815%!test ## ND array objects with complex number members 816%! obj = struct('a',{4+6i,5+7i;4+6i,5+7i}); 817%! obj(:,:,2) = obj; 818%! 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}}]]]'), 819%! obj ); 820 821%!test ## array of objects with DEEP complex number member 822%! assert( fromJSON('[{"a": {"b": {re:4, im:6}}},{"a": {re:5,im:7}}]'), 823%! [struct('a',struct('b',4+6i)),struct('a',5+7i)] ); 824 825%!test ## mismatch array (to cell array) of objects with DEEP complex number member 826%! assert( fromJSON('[{"a":{"b":{re:4, im:6}}}, {"c":{"d":{"e":{re:5,im:7}}}}]'), 827%! {struct('a',struct('b',4+6i)),struct('c',struct('d',struct('e',5+7i)))} ); 828 829%!test # array of complex number in algebraic format (extracircular parsing) 830%! assert( fromJSON('[[4+6i, 1+0i], [2i, 4i + 2]]'), 831%! [4+6i 1;2i 2+4i]); 832 833%!test ## complex number object mixed with number within array (test array parsing) 834%! assert( fromJSON('[[4,{re:1,im:0}],[{re:0,im:2}, {re:4,im:6}]]'), 835%! [4 1;2i 4+6i]); 836 837%!test ## complex number object within a structure (nested) 838%! assert( fromJSON('{"a": {re:1,im:5}}'), 839%! struct('a',1+5i)); 840 841%!test ## complex number object with structure array (nested) 842%! assert( fromJSON('[{"a":{re:1,im:5}},{"a":{re:2,im:5}}]'), 843%! struct('a',{1+5i,2+5i})); 844 845%!test ## complex number object deeply nested in object 846%! assert( fromJSON('{a:1,b:{c:2,d:{e:3,f:{re:3,im:5}}}}'), 847%! struct('a',1,'b',struct('c',2,'d',struct('e',3,'f',3+5i)))); 848 849############## file encoding error ###################### 850###### simulate parsing with non-ASCII character######### 851 852%!test ## leading file-encoding bytes (simulated with emoji) 853%! assert( fromJSON('4'), 4); % just ignore it, likely some file encoding taken in by fread 854 855%!test ## inside arrray 856%!warning <invalid> assert( fromJSON('[,4]'), [NaN, 4]); % convert to NaN 857 858%!test ## inside struct 859%!warning <invalid> assert( fromJSON('{"a":4}'), struct('a',NaN)); % convert to NaN 860 861%!test ## inside quotes 862%! assert( fromJSON('""'), ''); % SMILE! not invalid 863 864############### exotic JSON BIST ##################### 865 866%!test ## test octave inline fn (convert to inline) 867%! assert(fromJSON('"@@sin"'),@sin); 868%! assert(func2str(fromJSON('"@@(x)3*x"')),func2str(@(x)3*x)); 869 870%!test ## test apparent octave inline fn (do NOT convert to inline) 871%! assert(fromJSON('"@sin"'), '@sin'); 872%! assert(fromJSON('"@(x)3*x"'),'@(x)3*x'); 873 874%!test ## test octave inline fn with SARRAY=false (do NOT convert otherwise valid inline) 875%! assert(fromJSON('"@@sin"', false), '@@sin'); 876%! assert(fromJSON('"@@(x)3*x"', false),'@@(x)3*x'); 877 878%!test ## exotic object in structure 879%! assert( fromJSON('{"a":"[java.math.BigDecimal]"}'),struct('a','[java.math.BigDecimal]')); 880 881%!test ## exotic object (placeholder of class name) 882%! assert( fromJSON('"[java.math.BigDecimal]"'), 883%! '[java.math.BigDecimal]'); 884 885############### beautified or confusing JSON BIST ##################### 886 887%!test ## JSON with confusing '[]{},' AND missing quotes (hard string parse test) 888%! warning('off','all'); 889%! obj=fromJSON('[{a:"tes, {}: [ ] t"},"lkj{} sdf",im mi{}ing quotes]'); 890%! assert(obj,{struct('a',"tes, {}: [ ] t"), 'lkj{} sdf',NaN}) 891 892%!test ## beautified JSON object (extraneous whitespace/newline) 893%! assert( fromJSON("\n { \n\t\"a\"\t\n\n\n :\t\n\n\n 3\n\t} \n \n"), 894%! struct("a",3)) 895 896%!test ## beautified JSON string with array (test parse with extraneous whitespace) 897%! pretty_json ="{\n \"data\": [\n {\n \"vendor\": \"0x10de\",\n \"details\": {\n \"created\": \"2012-11-12T10:35:32Z\"\n },\n \"_status\": \"synced\"\n }\n ]\n}"; 898%! 899%! obj = struct('data', ... 900%! struct('vendor','0x10de', ... 901%! 'details', struct('created','2012-11-12T10:35:32Z'), ... 902%! '_status', 'synced' 903%! ) 904%! ); 905%! 906%! assert( fromJSON(pretty_json),obj,true); 907 908%!test ## garbage string with whitespace 909%!warning <invalid> assert( fromJSON("[1, gar bage]"),[1,NaN]) 910 911%!test ## garbage string with newline 912%!warning <invalid> assert( fromJSON("[1, \n gar \n bage]"),[1,NaN]) 913 914%!test ## garbage string with quotes 915%!warning <invalid> assert( fromJSON('[1,gar""bage]'),[1,NaN]) 916 917%!test ## garbage string with array brackets 918%!warning <invalid> assert( fromJSON('[1,gar[]bage]'),[1,NaN]) 919 920%!test ## garbage string with object brackets 921%!warning <invalid> assert( fromJSON('[1,{"a":gar{}bage}]'),{1,struct('a',NaN)}) 922 923%!test ## looks like fn, but is UNQUOTED string 924%!warning <invalid> assert(fromJSON('@@nofunc'),NaN); 925 926########## complex (non-numerical) JSON BIST ################ 927 928%!test ## a JSON string stored inside JSON string object 929%! json = "[ \n { \n \"plot\": [ \n { \n \"line1\": { \n \"x\": [ \n 1, \n 3 \n ], \n \"y\": [ \n 12, \n 32 \n ] \n } \n } \n ] \n } \n]"; 930%! assert( fromJSON(json,true), 931%! struct('plot',struct('line1',struct('x',[1,3],'y',[12,32])))); 932 933%!test ## jsondecode's Arrays with the same field names in the same order. 934%! json = '[{"x_id":"5ee28980fc9ab3","index":0,"guid":"b229d1de-f94a","latitude":-17.124067,"longitude":-61.161831,"friends":[{"id":0,"name":"Collins"},{"id":1,"name":"Hays"},{"id":2,"name":"Griffin"}]},{"x_id":"5ee28980dd7250","index":1,"guid":"39cee338-01fb","latitude":13.205994,"longitude":-37.276231,"friends":[{"id":0,"name":"Osborn"},{"id":1,"name":"Mcdowell"},{"id":2,"name":"Jewel"}]},{"x_id":"5ee289802422ac","index":2,"guid":"3db8d55a-663e","latitude":-35.453456,"longitude":14.080287,"friends":[{"id":0,"name":"Socorro"},{"id":1,"name":"Darla"},{"id":2,"name":"Leanne"}]}]'; 935%! 936%! f1 = num2cell(struct ('id', {0 1 2}, 'name', {'Collins' 'Hays' 'Griffin'})); 937%! f2 = num2cell(struct ('id', {0 1 2}, 'name', {'Osborn' 'Mcdowell' 'Jewel'})); 938%! f3 = num2cell(struct ('id', {0 1 2}, 'name', {'Socorro' 'Darla' 'Leanne'})); 939%! 940%! keys = {'x_id','index','guid','latitude','longitude','friends'}; 941%! val1 = {'5ee28980fc9ab3', 0, 'b229d1de-f94a', -17.124067, -61.161831, f1}; 942%! val2 = {'5ee28980dd7250', 1, '39cee338-01fb', 13.205994, -37.276231, f2}; 943%! val3 = {'5ee289802422ac', 2, '3db8d55a-663e', -35.453456, 14.080287, f3}; 944%! 945%! exp = cell2struct({val1{:};val2{:};val3{:}}',keys)'; 946%! fromJSON(json); # this may be confusing mix of array and non-cell array, but NO fail allowed 947%! assert (fromJSON(json, false), num2cell(exp)); 948 949 950%!test ## from firefox active-stream.discovery_stream.json (UGH! url link as object key) 951%! 952%! json = '[{"feeds":{"https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=$locale®ion=$region&count=30":{"lastUpdated":1640020188467,"data":{"settings":{"domainAffinityParameterSets":{"default":{"recencyFactor":0.5,"frequencyFactor":0.5,"combinedDomainFactor":0.5,"perfectFrequencyVisits":10,"perfectCombinedDomainScore":2,"multiDomainBoost":0,"itemScoreFactor":1}}}}}}}]'; 953%! exp = struct("feeds", 954%! struct('https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=$locale®ion=$region&count=30', 955%! struct("lastUpdated",1640020188467, 956%! "data", 957%! struct("settings", 958%! struct("domainAffinityParameterSets", 959%! struct("default", 960%! struct("recencyFactor",0.5, 961%! "frequencyFactor",0.5, 962%! "combinedDomainFactor",0.5, 963%! "perfectFrequencyVisits",10, 964%! "perfectCombinedDomainScore",2, 965%! "multiDomainBoost",0, 966%! "itemScoreFactor",1 967%! ) 968%! ) 969%! ) 970%! ) 971%! ) 972%! ) 973%! ); 974%! assert (fromJSON(json, true), exp ); 975%! assert (fromJSON(json, false), {exp}); 976 977