savejson.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. function json=savejson(rootname,obj,varargin)
  2. %
  3. % json=savejson(rootname,obj,filename)
  4. % or
  5. % json=savejson(rootname,obj,opt)
  6. % json=savejson(rootname,obj,'param1',value1,'param2',value2,...)
  7. %
  8. % convert a MATLAB object (cell, struct or array) into a JSON (JavaScript
  9. % Object Notation) string
  10. %
  11. % author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu)
  12. % created on 2011/09/09
  13. %
  14. % $Id: savejson.m 460 2015-01-03 00:30:45Z fangq $
  15. %
  16. % input:
  17. % rootname: the name of the root-object, when set to '', the root name
  18. % is ignored, however, when opt.ForceRootName is set to 1 (see below),
  19. % the MATLAB variable name will be used as the root name.
  20. % obj: a MATLAB object (array, cell, cell array, struct, struct array).
  21. % filename: a string for the file name to save the output JSON data.
  22. % opt: a struct for additional options, ignore to use default values.
  23. % opt can have the following fields (first in [.|.] is the default)
  24. %
  25. % opt.FileName [''|string]: a file name to save the output JSON data
  26. % opt.FloatFormat ['%.10g'|string]: format to show each numeric element
  27. % of a 1D/2D array;
  28. % opt.ArrayIndent [1|0]: if 1, output explicit data array with
  29. % precedent indentation; if 0, no indentation
  30. % opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D
  31. % array in JSON array format; if sets to 1, an
  32. % array will be shown as a struct with fields
  33. % "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for
  34. % sparse arrays, the non-zero elements will be
  35. % saved to _ArrayData_ field in triplet-format i.e.
  36. % (ix,iy,val) and "_ArrayIsSparse_" will be added
  37. % with a value of 1; for a complex array, the
  38. % _ArrayData_ array will include two columns
  39. % (4 for sparse) to record the real and imaginary
  40. % parts, and also "_ArrayIsComplex_":1 is added.
  41. % opt.ParseLogical [0|1]: if this is set to 1, logical array elem
  42. % will use true/false rather than 1/0.
  43. % opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single
  44. % numerical element will be shown without a square
  45. % bracket, unless it is the root object; if 0, square
  46. % brackets are forced for any numerical arrays.
  47. % opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson
  48. % will use the name of the passed obj variable as the
  49. % root object name; if obj is an expression and
  50. % does not have a name, 'root' will be used; if this
  51. % is set to 0 and rootname is empty, the root level
  52. % will be merged down to the lower level.
  53. % opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern
  54. % to represent +/-Inf. The matched pattern is '([-+]*)Inf'
  55. % and $1 represents the sign. For those who want to use
  56. % 1e999 to represent Inf, they can set opt.Inf to '$11e999'
  57. % opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern
  58. % to represent NaN
  59. % opt.JSONP [''|string]: to generate a JSONP output (JSON with padding),
  60. % for example, if opt.JSONP='foo', the JSON data is
  61. % wrapped inside a function call as 'foo(...);'
  62. % opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson
  63. % back to the string form
  64. % opt.SaveBinary [0|1]: 1 - save the JSON file in binary mode; 0 - text mode.
  65. % opt.Compact [0|1]: 1- out compact JSON format (remove all newlines and tabs)
  66. %
  67. % opt can be replaced by a list of ('param',value) pairs. The param
  68. % string is equivallent to a field in opt and is case sensitive.
  69. % output:
  70. % json: a string in the JSON format (see http://json.org)
  71. %
  72. % examples:
  73. % jsonmesh=struct('MeshNode',[0 0 0;1 0 0;0 1 0;1 1 0;0 0 1;1 0 1;0 1 1;1 1 1],...
  74. % 'MeshTetra',[1 2 4 8;1 3 4 8;1 2 6 8;1 5 6 8;1 5 7 8;1 3 7 8],...
  75. % 'MeshTri',[1 2 4;1 2 6;1 3 4;1 3 7;1 5 6;1 5 7;...
  76. % 2 8 4;2 8 6;3 8 4;3 8 7;5 8 6;5 8 7],...
  77. % 'MeshCreator','FangQ','MeshTitle','T6 Cube',...
  78. % 'SpecialData',[nan, inf, -inf]);
  79. % savejson('jmesh',jsonmesh)
  80. % savejson('',jsonmesh,'ArrayIndent',0,'FloatFormat','\t%.5g')
  81. %
  82. % license:
  83. % BSD, see LICENSE_BSD.txt files for details
  84. %
  85. % -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab)
  86. %
  87. if(nargin==1)
  88. varname=inputname(1);
  89. obj=rootname;
  90. if(isempty(varname))
  91. varname='root';
  92. end
  93. rootname=varname;
  94. else
  95. varname=inputname(2);
  96. end
  97. if(length(varargin)==1 && ischar(varargin{1}))
  98. opt=struct('FileName',varargin{1});
  99. else
  100. opt=varargin2struct(varargin{:});
  101. end
  102. opt.IsOctave=exist('OCTAVE_VERSION','builtin');
  103. rootisarray=0;
  104. rootlevel=1;
  105. forceroot=jsonopt('ForceRootName',0,opt);
  106. if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0)
  107. rootisarray=1;
  108. rootlevel=0;
  109. else
  110. if(isempty(rootname))
  111. rootname=varname;
  112. end
  113. end
  114. if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot)
  115. rootname='root';
  116. end
  117. whitespaces=struct('tab',sprintf('\t'),'newline',sprintf('\n'),'sep',sprintf(',\n'));
  118. if(jsonopt('Compact',0,opt)==1)
  119. whitespaces=struct('tab','','newline','','sep',',');
  120. end
  121. if(~isfield(opt,'whitespaces_'))
  122. opt.whitespaces_=whitespaces;
  123. end
  124. nl=whitespaces.newline;
  125. json=obj2json(rootname,obj,rootlevel,opt);
  126. if(rootisarray)
  127. json=sprintf('%s%s',json,nl);
  128. else
  129. json=sprintf('{%s%s%s}\n',nl,json,nl);
  130. end
  131. jsonp=jsonopt('JSONP','',opt);
  132. if(~isempty(jsonp))
  133. json=sprintf('%s(%s);%s',jsonp,json,nl);
  134. end
  135. % save to a file if FileName is set, suggested by Patrick Rapin
  136. if(~isempty(jsonopt('FileName','',opt)))
  137. if(jsonopt('SaveBinary',0,opt)==1)
  138. fid = fopen(opt.FileName, 'wb');
  139. fwrite(fid,json);
  140. else
  141. fid = fopen(opt.FileName, 'wt');
  142. fwrite(fid,json,'char');
  143. end
  144. fclose(fid);
  145. end
  146. %%-------------------------------------------------------------------------
  147. function txt=obj2json(name,item,level,varargin)
  148. if(iscell(item))
  149. txt=cell2json(name,item,level,varargin{:});
  150. elseif(isstruct(item))
  151. txt=struct2json(name,item,level,varargin{:});
  152. elseif(ischar(item))
  153. txt=str2json(name,item,level,varargin{:});
  154. else
  155. txt=mat2json(name,item,level,varargin{:});
  156. end
  157. %%-------------------------------------------------------------------------
  158. function txt=cell2json(name,item,level,varargin)
  159. txt='';
  160. if(~iscell(item))
  161. error('input is not a cell');
  162. end
  163. dim=size(item);
  164. if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now
  165. item=reshape(item,dim(1),numel(item)/dim(1));
  166. dim=size(item);
  167. end
  168. len=numel(item);
  169. ws=jsonopt('whitespaces_',struct('tab',sprintf('\t'),'newline',sprintf('\n'),'sep',sprintf(',\n')),varargin{:});
  170. padding0=repmat(ws.tab,1,level);
  171. padding2=repmat(ws.tab,1,level+1);
  172. nl=ws.newline;
  173. if(len>1)
  174. if(~isempty(name))
  175. txt=sprintf('%s"%s": [%s',padding0, checkname(name,varargin{:}),nl); name='';
  176. else
  177. txt=sprintf('%s[%s',padding0,nl);
  178. end
  179. elseif(len==0)
  180. if(~isempty(name))
  181. txt=sprintf('%s"%s": []',padding0, checkname(name,varargin{:})); name='';
  182. else
  183. txt=sprintf('%s[]',padding0);
  184. end
  185. end
  186. for j=1:dim(2)
  187. if(dim(1)>1) txt=sprintf('%s%s[%s',txt,padding2,nl); end
  188. for i=1:dim(1)
  189. txt=sprintf('%s%s',txt,obj2json(name,item{i,j},level+(dim(1)>1)+1,varargin{:}));
  190. if(i<dim(1)) txt=sprintf('%s%s',txt,sprintf(',%s',nl)); end
  191. end
  192. if(dim(1)>1) txt=sprintf('%s%s%s]',txt,nl,padding2); end
  193. if(j<dim(2)) txt=sprintf('%s%s',txt,sprintf(',%s',nl)); end
  194. %if(j==dim(2)) txt=sprintf('%s%s',txt,sprintf(',%s',nl)); end
  195. end
  196. if(len>1) txt=sprintf('%s%s%s]',txt,nl,padding0); end
  197. %%-------------------------------------------------------------------------
  198. function txt=struct2json(name,item,level,varargin)
  199. txt='';
  200. if(~isstruct(item))
  201. error('input is not a struct');
  202. end
  203. dim=size(item);
  204. if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now
  205. item=reshape(item,dim(1),numel(item)/dim(1));
  206. dim=size(item);
  207. end
  208. len=numel(item);
  209. ws=struct('tab',sprintf('\t'),'newline',sprintf('\n'));
  210. ws=jsonopt('whitespaces_',ws,varargin{:});
  211. padding0=repmat(ws.tab,1,level);
  212. padding2=repmat(ws.tab,1,level+1);
  213. padding1=repmat(ws.tab,1,level+(dim(1)>1)+(len>1));
  214. nl=ws.newline;
  215. if(~isempty(name))
  216. if(len>1) txt=sprintf('%s"%s": [%s',padding0,checkname(name,varargin{:}),nl); end
  217. else
  218. if(len>1) txt=sprintf('%s[%s',padding0,nl); end
  219. end
  220. for j=1:dim(2)
  221. if(dim(1)>1) txt=sprintf('%s%s[%s',txt,padding2,nl); end
  222. for i=1:dim(1)
  223. names = fieldnames(item(i,j));
  224. if(~isempty(name) && len==1)
  225. txt=sprintf('%s%s"%s": {%s',txt,padding1, checkname(name,varargin{:}),nl);
  226. else
  227. txt=sprintf('%s%s{%s',txt,padding1,nl);
  228. end
  229. if(~isempty(names))
  230. for e=1:length(names)
  231. txt=sprintf('%s%s',txt,obj2json(names{e},getfield(item(i,j),...
  232. names{e}),level+(dim(1)>1)+1+(len>1),varargin{:}));
  233. if(e<length(names)) txt=sprintf('%s%s',txt,','); end
  234. txt=sprintf('%s%s',txt,nl);
  235. end
  236. end
  237. txt=sprintf('%s%s}',txt,padding1);
  238. if(i<dim(1)) txt=sprintf('%s%s',txt,sprintf(',%s',nl)); end
  239. end
  240. if(dim(1)>1) txt=sprintf('%s%s%s]',txt,nl,padding2); end
  241. if(j<dim(2)) txt=sprintf('%s%s',txt,sprintf(',%s',nl)); end
  242. end
  243. if(len>1) txt=sprintf('%s%s%s]',txt,nl,padding0); end
  244. %%-------------------------------------------------------------------------
  245. function txt=str2json(name,item,level,varargin)
  246. txt='';
  247. if(~ischar(item))
  248. error('input is not a string');
  249. end
  250. item=reshape(item, max(size(item),[1 0]));
  251. len=size(item,1);
  252. ws=struct('tab',sprintf('\t'),'newline',sprintf('\n'),'sep',sprintf(',\n'));
  253. ws=jsonopt('whitespaces_',ws,varargin{:});
  254. padding1=repmat(ws.tab,1,level);
  255. padding0=repmat(ws.tab,1,level+1);
  256. nl=ws.newline;
  257. sep=ws.sep;
  258. if(~isempty(name))
  259. if(len>1) txt=sprintf('%s"%s": [%s',padding1,checkname(name,varargin{:}),nl); end
  260. else
  261. if(len>1) txt=sprintf('%s[%s',padding1,nl); end
  262. end
  263. isoct=jsonopt('IsOctave',0,varargin{:});
  264. for e=1:len
  265. if(isoct)
  266. val=regexprep(item(e,:),'\\','\\');
  267. val=regexprep(val,'"','\"');
  268. val=regexprep(val,'^"','\"');
  269. else
  270. val=regexprep(item(e,:),'\\','\\\\');
  271. val=regexprep(val,'"','\\"');
  272. val=regexprep(val,'^"','\\"');
  273. end
  274. val=escapejsonstring(val);
  275. if(len==1)
  276. obj=['"' checkname(name,varargin{:}) '": ' '"',val,'"'];
  277. if(isempty(name)) obj=['"',val,'"']; end
  278. txt=sprintf('%s%s%s%s',txt,padding1,obj);
  279. else
  280. txt=sprintf('%s%s%s%s',txt,padding0,['"',val,'"']);
  281. end
  282. if(e==len) sep=''; end
  283. txt=sprintf('%s%s',txt,sep);
  284. end
  285. if(len>1) txt=sprintf('%s%s%s%s',txt,nl,padding1,']'); end
  286. %%-------------------------------------------------------------------------
  287. function txt=mat2json(name,item,level,varargin)
  288. if(~isnumeric(item) && ~islogical(item))
  289. error('input is not an array');
  290. end
  291. ws=struct('tab',sprintf('\t'),'newline',sprintf('\n'),'sep',sprintf(',\n'));
  292. ws=jsonopt('whitespaces_',ws,varargin{:});
  293. padding1=repmat(ws.tab,1,level);
  294. padding0=repmat(ws.tab,1,level+1);
  295. nl=ws.newline;
  296. sep=ws.sep;
  297. if(length(size(item))>2 || issparse(item) || ~isreal(item) || ...
  298. isempty(item) ||jsonopt('ArrayToStruct',0,varargin{:}))
  299. if(isempty(name))
  300. txt=sprintf('%s{%s%s"_ArrayType_": "%s",%s%s"_ArraySize_": %s,%s',...
  301. padding1,nl,padding0,class(item),nl,padding0,regexprep(mat2str(size(item)),'\s+',','),nl);
  302. else
  303. txt=sprintf('%s"%s": {%s%s"_ArrayType_": "%s",%s%s"_ArraySize_": %s,%s',...
  304. padding1,checkname(name,varargin{:}),nl,padding0,class(item),nl,padding0,regexprep(mat2str(size(item)),'\s+',','),nl);
  305. end
  306. else
  307. if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1 && level>0)
  308. numtxt=regexprep(regexprep(matdata2json(item,level+1,varargin{:}),'^\[',''),']','');
  309. else
  310. numtxt=matdata2json(item,level+1,varargin{:});
  311. end
  312. if(isempty(name))
  313. txt=sprintf('%s%s',padding1,numtxt);
  314. else
  315. if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1)
  316. txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),numtxt);
  317. else
  318. txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),numtxt);
  319. end
  320. end
  321. return;
  322. end
  323. dataformat='%s%s%s%s%s';
  324. if(issparse(item))
  325. [ix,iy]=find(item);
  326. data=full(item(find(item)));
  327. if(~isreal(item))
  328. data=[real(data(:)),imag(data(:))];
  329. if(size(item,1)==1)
  330. % Kludge to have data's 'transposedness' match item's.
  331. % (Necessary for complex row vector handling below.)
  332. data=data';
  333. end
  334. txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sep);
  335. end
  336. txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sep);
  337. if(size(item,1)==1)
  338. % Row vector, store only column indices.
  339. txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',...
  340. matdata2json([iy(:),data'],level+2,varargin{:}), nl);
  341. elseif(size(item,2)==1)
  342. % Column vector, store only row indices.
  343. txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',...
  344. matdata2json([ix,data],level+2,varargin{:}), nl);
  345. else
  346. % General case, store row and column indices.
  347. txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',...
  348. matdata2json([ix,iy,data],level+2,varargin{:}), nl);
  349. end
  350. else
  351. if(isreal(item))
  352. txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',...
  353. matdata2json(item(:)',level+2,varargin{:}), nl);
  354. else
  355. txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sep);
  356. txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',...
  357. matdata2json([real(item(:)) imag(item(:))],level+2,varargin{:}), nl);
  358. end
  359. end
  360. txt=sprintf('%s%s%s',txt,padding1,'}');
  361. %%-------------------------------------------------------------------------
  362. function txt=matdata2json(mat,level,varargin)
  363. ws=struct('tab',sprintf('\t'),'newline',sprintf('\n'),'sep',sprintf(',\n'));
  364. ws=jsonopt('whitespaces_',ws,varargin{:});
  365. tab=ws.tab;
  366. nl=ws.newline;
  367. if(size(mat,1)==1)
  368. pre='';
  369. post='';
  370. level=level-1;
  371. else
  372. pre=sprintf('[%s',nl);
  373. post=sprintf('%s%s]',nl,repmat(tab,1,level-1));
  374. end
  375. if(isempty(mat))
  376. txt='null';
  377. return;
  378. end
  379. floatformat=jsonopt('FloatFormat','%.10g',varargin{:});
  380. %if(numel(mat)>1)
  381. formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf('],%s',nl)]];
  382. %else
  383. % formatstr=[repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf(',\n')]];
  384. %end
  385. if(nargin>=2 && size(mat,1)>1 && jsonopt('ArrayIndent',1,varargin{:})==1)
  386. formatstr=[repmat(tab,1,level) formatstr];
  387. end
  388. txt=sprintf(formatstr,mat');
  389. txt(end-length(nl):end)=[];
  390. if(islogical(mat) && jsonopt('ParseLogical',0,varargin{:})==1)
  391. txt=regexprep(txt,'1','true');
  392. txt=regexprep(txt,'0','false');
  393. end
  394. %txt=regexprep(mat2str(mat),'\s+',',');
  395. %txt=regexprep(txt,';',sprintf('],\n['));
  396. % if(nargin>=2 && size(mat,1)>1)
  397. % txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']);
  398. % end
  399. txt=[pre txt post];
  400. if(any(isinf(mat(:))))
  401. txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:}));
  402. end
  403. if(any(isnan(mat(:))))
  404. txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:}));
  405. end
  406. %%-------------------------------------------------------------------------
  407. function newname=checkname(name,varargin)
  408. isunpack=jsonopt('UnpackHex',1,varargin{:});
  409. newname=name;
  410. if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once')))
  411. return
  412. end
  413. if(isunpack)
  414. isoct=jsonopt('IsOctave',0,varargin{:});
  415. if(~isoct)
  416. newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}');
  417. else
  418. pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start');
  419. pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end');
  420. if(isempty(pos)) return; end
  421. str0=name;
  422. pos0=[0 pend(:)' length(name)];
  423. newname='';
  424. for i=1:length(pos)
  425. newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))];
  426. end
  427. if(pos(end)~=length(name))
  428. newname=[newname str0(pos0(end-1)+1:pos0(end))];
  429. end
  430. end
  431. end
  432. %%-------------------------------------------------------------------------
  433. function newstr=escapejsonstring(str)
  434. newstr=str;
  435. isoct=exist('OCTAVE_VERSION','builtin');
  436. if(isoct)
  437. vv=sscanf(OCTAVE_VERSION,'%f');
  438. if(vv(1)>=3.8) isoct=0; end
  439. end
  440. if(isoct)
  441. escapechars={'\a','\f','\n','\r','\t','\v'};
  442. for i=1:length(escapechars);
  443. newstr=regexprep(newstr,escapechars{i},escapechars{i});
  444. end
  445. else
  446. escapechars={'\a','\b','\f','\n','\r','\t','\v'};
  447. for i=1:length(escapechars);
  448. newstr=regexprep(newstr,escapechars{i},regexprep(escapechars{i},'\\','\\\\'));
  449. end
  450. end