function data = loadjson(fname,varargin) % % data=loadjson(fname,opt) % or % data=loadjson(fname,'param1',value1,'param2',value2,...) % % parse a JSON (JavaScript Object Notation) file or string % % authors:Qianqian Fang (q.fang neu.edu) % created on 2011/09/09, including previous works from % % Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 % created on 2009/11/02 % François Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 % created on 2009/03/22 % Joel Feenstra: % http://www.mathworks.com/matlabcentral/fileexchange/20565 % created on 2008/07/03 % % $Id$ % % input: % fname: input file name, if fname contains "{}" or "[]", fname % will be interpreted as a JSON string % opt: a struct to store parsing options, opt can be replaced by % a list of ('param',value) pairs - the param string is equivallent % to a field in opt. opt can have the following % fields (first in [.|.] is the default) % % opt.SimplifyCell [0|1]: if set to 1, loadjson will call cell2mat % for each element of the JSON data, and group % arrays based on the cell2mat rules. % opt.FastArrayParser [1|0 or integer]: if set to 1, use a % speed-optimized array parser when loading an % array object. The fast array parser may % collapse block arrays into a single large % array similar to rules defined in cell2mat; 0 to % use a legacy parser; if set to a larger-than-1 % value, this option will specify the minimum % dimension to enable the fast array parser. For % example, if the input is a 3D array, setting % FastArrayParser to 1 will return a 3D array; % setting to 2 will return a cell array of 2D % arrays; setting to 3 will return to a 2D cell % array of 1D vectors; setting to 4 will return a % 3D cell array. % opt.ShowProgress [0|1]: if set to 1, loadjson displays a progress bar. % opt.ParseStringArray [0|1]: if set to 1, loadjson displays a progress bar. % % output: % dat: a cell array, where {...} blocks are converted into cell arrays, % and [...] are converted to arrays % % examples: % dat=loadjson('{"obj":{"string":"value","array":[1,2,3]}}') % dat=loadjson(['examples' filesep 'example1.json']) % dat=loadjson(['examples' filesep 'example1.json'],'SimplifyCell',1) % % license: % BSD, see LICENSE_BSD.txt file for details % % -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) % import jsonlab.* global pos index_esc isoct arraytoken if(regexp(fname,'^\s*(?:\[.*\])|(?:\{.*\})\s*$','once')) string=fname; elseif(exist(fname,'file')) try string = fileread(fname); catch try string = urlread(['file://',fname]); catch string = urlread(['file://',fullfile(pwd,fname)]); end end else error('input file does not exist'); end pos = 1; len = length(string); inStr = string; isoct=exist('OCTAVE_VERSION','builtin'); arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); jstr=regexprep(inStr,'\\\\',' '); escquote=regexp(jstr,'\\"'); arraytoken=sort([arraytoken escquote]); % String delimiters and escape chars identified to improve speed: esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); index_esc = 1; opt=varargin2struct(varargin{:}); if(jsonopt('ShowProgress',0,opt)==1) opt.progressbar_=waitbar(0,'loading ...'); end jsoncount=1; while pos <= len switch(next_char(inStr)) case '{' data{jsoncount} = parse_object(inStr, esc, opt); case '[' data{jsoncount} = parse_array(inStr, esc, opt); otherwise error_pos('Outer level structure must be an object or an array',inStr); end jsoncount=jsoncount+1; end % while jsoncount=length(data); if(jsoncount==1 && iscell(data)) data=data{1}; end if(isfield(opt,'progressbar_')) close(opt.progressbar_); end %%------------------------------------------------------------------------- function object = parse_object(inStr, esc, varargin) import jsonlab.* parse_char(inStr, '{'); object = []; if next_char(inStr) ~= '}' while 1 str = parseStr(inStr, esc, varargin{:}); if isempty(str) error_pos('Name of value at position %d cannot be empty',inStr); end parse_char(inStr, ':'); val = parse_value(inStr, esc, varargin{:}); object.(valid_field(str))=val; if next_char(inStr) == '}' break; end parse_char(inStr, ','); end end parse_char(inStr, '}'); if(isstruct(object)) object=struct2jdata(object); end %%------------------------------------------------------------------------- function object = parse_array(inStr, esc, varargin) % JSON array is written in row-major order import jsonlab.* global pos isoct parse_char(inStr, '['); object = cell(0, 1); dim2=[]; arraydepth=jsonopt('JSONLAB_ArrayDepth_',1,varargin{:}); pbar=-1; if(isfield(varargin{1},'progressbar_')) pbar=varargin{1}.progressbar_; end if next_char(inStr) ~= ']' if(jsonopt('FastArrayParser',1,varargin{:})>=1 && arraydepth>=jsonopt('FastArrayParser',1,varargin{:})) [endpos, e1l, e1r]=matching_bracket(inStr,pos); arraystr=['[' inStr(pos:endpos)]; arraystr=regexprep(arraystr,'"_NaN_"','NaN'); arraystr=regexprep(arraystr,'"([-+]*)_Inf_"','$1Inf'); arraystr(arraystr==sprintf('\n'))=[]; arraystr(arraystr==sprintf('\r'))=[]; %arraystr=regexprep(arraystr,'\s*,',','); % this is slow,sometimes needed if(~isempty(e1l) && ~isempty(e1r)) % the array is in 2D or higher D astr=inStr((e1l+1):(e1r-1)); astr=regexprep(astr,'"_NaN_"','NaN'); astr=regexprep(astr,'"([-+]*)_Inf_"','$1Inf'); astr(astr==sprintf('\n'))=[]; astr(astr==sprintf('\r'))=[]; astr(astr==' ')=''; if(isempty(find(astr=='[', 1))) % array is 2D dim2=length(sscanf(astr,'%f,',[1 inf])); end else % array is 1D astr=arraystr(2:end-1); astr(astr==' ')=''; [obj, count, errmsg, nextidx]=sscanf(astr,'%f,',[1,inf]); if(nextidx>=length(astr)-1) object=obj; pos=endpos; parse_char(inStr, ']'); return; end end try if(~isempty(dim2)) astr=arraystr; astr(astr=='[')=''; astr(astr==']')=''; astr=regexprep(astr,'\s*$',''); astr(astr==' ')=''; [obj, count, errmsg, nextidx]=sscanf(astr,'%f,',inf); if(nextidx>=length(astr)-1) object=reshape(obj,dim2,numel(obj)/dim2)'; pos=endpos; parse_char(inStr, ']'); if(pbar>0) waitbar(pos/length(inStr),pbar,'loading ...'); end return; end end arraystr=regexprep(arraystr,'\]\s*,','];'); catch end else arraystr='['; end try arraystr=regexprep(arraystr,'^\s*\[','{','once'); arraystr=regexprep(arraystr,'\]\s*$','}','once'); if(isoct && regexp(arraystr,'"','once')) error('Octave eval can produce empty cells for JSON-like input'); end if(regexp(arraystr,':','once')) error('One can not use MATLAB-like ":" construct inside a JSON array'); end if(jsonopt('ParseStringArray',0,varargin{:})==0) arraystr=regexprep(arraystr,'\"',''''); end object=eval(arraystr); if(iscell(object)) object=cellfun(@unescapejsonstring,object,'UniformOutput',false); end pos=endpos; catch while 1 newopt=varargin2struct(varargin{:},'JSONLAB_ArrayDepth_',arraydepth+1); val = parse_value(inStr, esc, newopt); object{end+1} = val; if next_char(inStr) == ']' break; end parse_char(inStr, ','); end end end if(jsonopt('SimplifyCell',0,varargin{:})==1) try oldobj=object; object=cell2mat(object')'; if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) object=oldobj; elseif(size(object,1)>1 && ismatrix(object)) object=object'; end catch end end parse_char(inStr, ']'); if(pbar>0) waitbar(pos/length(inStr),pbar,'loading ...'); end %%------------------------------------------------------------------------- function parse_char(inStr, c) import jsonlab.* global pos pos=skip_whitespace(pos, inStr); if pos > length(inStr) || inStr(pos) ~= c error_pos(sprintf('Expected %c at position %%d', c),inStr); else pos = pos + 1; pos=skip_whitespace(pos, inStr); end %%------------------------------------------------------------------------- function c = next_char(inStr) import jsonlab.* global pos pos=skip_whitespace(pos, inStr); if pos > length(inStr) c = []; else c = inStr(pos); end %%------------------------------------------------------------------------- function newpos=skip_whitespace(pos, inStr) import jsonlab.* newpos=pos; while newpos <= length(inStr) && isspace(inStr(newpos)) newpos = newpos + 1; end %%------------------------------------------------------------------------- function str = parseStr(inStr, esc, varargin) import jsonlab.* global pos index_esc % len, ns = length(inStr), keyboard if inStr(pos) ~= '"' error_pos('String starting with " expected at position %d',inStr); else pos = pos + 1; end str = ''; while pos <= length(inStr) while index_esc <= length(esc) && esc(index_esc) < pos index_esc = index_esc + 1; end if index_esc > length(esc) str = [str inStr(pos:end)]; pos = length(inStr) + 1; break; else str = [str inStr(pos:esc(index_esc)-1)]; pos = esc(index_esc); end nstr = length(str); switch inStr(pos) case '"' pos = pos + 1; if(~isempty(str)) if(strcmp(str,'_Inf_')) str=Inf; elseif(strcmp(str,'-_Inf_')) str=-Inf; elseif(strcmp(str,'_NaN_')) str=NaN; end end return; case '\' if pos+1 > length(inStr) error_pos('End of file reached right after escape character',inStr); end pos = pos + 1; switch inStr(pos) case {'"' '\' '/'} str(nstr+1) = inStr(pos); pos = pos + 1; case {'b' 'f' 'n' 'r' 't'} str(nstr+1) = sprintf(['\' inStr(pos)]); pos = pos + 1; case 'u' if pos+4 > length(inStr) error_pos('End of file reached in escaped unicode character',inStr); end str(nstr+(1:6)) = inStr(pos-1:pos+4); pos = pos + 5; end otherwise % should never happen str(nstr+1) = inStr(pos); keyboard; pos = pos + 1; end end str=unescapejsonstring(str); error_pos('End of file while expecting end of inStr',inStr); %%------------------------------------------------------------------------- function num = parse_number(inStr, varargin) import jsonlab.* global pos isoct currstr=inStr(pos:min(pos+30,end)); if(isoct~=0) numstr=regexp(currstr,'^\s*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?','end'); [num] = sscanf(currstr, '%f', 1); delta=numstr+1; else [num, one, err, delta] = sscanf(currstr, '%f', 1); if ~isempty(err) error_pos('Error reading number at position %d',inStr); end end pos = pos + delta-1; %%------------------------------------------------------------------------- function val = parse_value(inStr, esc, varargin) import jsonlab.* global pos len=length(inStr); if(isfield(varargin{1},'progressbar_')) waitbar(pos/len,varargin{1}.progressbar_,'loading ...'); end switch(inStr(pos)) case '"' val = parseStr(inStr, esc, varargin{:}); return; case '[' val = parse_array(inStr, esc, varargin{:}); return; case '{' val = parse_object(inStr, esc, varargin{:}); return; case {'-','0','1','2','3','4','5','6','7','8','9'} val = parse_number(inStr, varargin{:}); return; case 't' if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'true') val = true; pos = pos + 4; return; end case 'f' if pos+4 <= len && strcmpi(inStr(pos:pos+4), 'false') val = false; pos = pos + 5; return; end case 'n' if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'null') val = []; pos = pos + 4; return; end end error_pos('Value expected at position %d',inStr); %%------------------------------------------------------------------------- function error_pos(msg, inStr) import jsonlab.* global pos len poShow = max(min([pos-15 pos-1 pos pos+20],len),1); if poShow(3) == poShow(2) poShow(3:4) = poShow(2)+[0 -1]; % display nothing after end msg = [sprintf(msg, pos) ': ' ... inStr(poShow(1):poShow(2)) '' inStr(poShow(3):poShow(4)) ]; error( ['JSONparser:invalidFormat: ' msg] ); %%------------------------------------------------------------------------- function str = valid_field(str) import jsonlab.* global isoct % From MATLAB doc: field names must begin with a letter, which may be % followed by any combination of letters, digits, and underscores. % Invalid characters will be converted to underscores, and the prefix % "x0x[Hex code]_" will be added if the first character is not a letter. pos=regexp(str,'^[^A-Za-z]','once'); if(~isempty(pos)) if(~isoct && str(1)+0 > 255) str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); else str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); end end if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end if(~isoct) str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); else pos=regexp(str,'[^0-9A-Za-z_]'); if(isempty(pos)) return; end str0=str; pos0=[0 pos(:)' length(str)]; str=''; for i=1:length(pos) str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; end if(pos(end)~=length(str)) str=[str str0(pos0(end-1)+1:pos0(end))]; end end %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; %%------------------------------------------------------------------------- function endpos = matching_quote(str,pos) import jsonlab.* len=length(str); while(pos1 && str(pos-1)=='\')) endpos=pos; return; end end pos=pos+1; end error('unmatched quotation mark'); %%------------------------------------------------------------------------- function [endpos, e1l, e1r, maxlevel] = matching_bracket(str,pos) import jsonlab.* global arraytoken level=1; maxlevel=level; endpos=0; bpos=arraytoken(arraytoken>=pos); tokens=str(bpos); len=length(tokens); pos=1; e1l=[]; e1r=[]; while(pos<=len) c=tokens(pos); if(c==']') level=level-1; if(isempty(e1r)) e1r=bpos(pos); end if(level==0) endpos=bpos(pos); return end elseif(c=='[') if(isempty(e1l)) e1l=bpos(pos); end level=level+1; maxlevel=max(maxlevel,level); elseif(c=='"') pos=matching_quote(tokens,pos+1); end pos=pos+1; end if(endpos==0) error('unmatched "]"'); end function newstr=unescapejsonstring(str) import jsonlab.* newstr=str; if(~ischar(str)) return; end escapechars={'\\','\"','\/','\a','\b','\f','\n','\r','\t','\v'}; for i=1:length(escapechars); newstr=regexprep(newstr,regexprep(escapechars{i},'\\','\\\\'), escapechars{i}); end newstr=regexprep(newstr,'\\u([0-9A-Fa-f]{4})', '${char(base2dec($1,16))}');