saveubjson.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. function json=saveubjson(rootname,obj,varargin)
  2. %
  3. % json=saveubjson(rootname,obj,filename)
  4. % or
  5. % json=saveubjson(rootname,obj,opt)
  6. % json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...)
  7. %
  8. % convert a MATLAB object (cell, struct or array) into a Universal
  9. % Binary JSON (UBJSON) binary string
  10. %
  11. % author: Qianqian Fang (fangq<at> nmr.mgh.harvard.edu)
  12. % created on 2013/08/17
  13. %
  14. % $Id: saveubjson.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 UBJSON 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.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D
  27. % array in JSON array format; if sets to 1, an
  28. % array will be shown as a struct with fields
  29. % "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for
  30. % sparse arrays, the non-zero elements will be
  31. % saved to _ArrayData_ field in triplet-format i.e.
  32. % (ix,iy,val) and "_ArrayIsSparse_" will be added
  33. % with a value of 1; for a complex array, the
  34. % _ArrayData_ array will include two columns
  35. % (4 for sparse) to record the real and imaginary
  36. % parts, and also "_ArrayIsComplex_":1 is added.
  37. % opt.ParseLogical [1|0]: if this is set to 1, logical array elem
  38. % will use true/false rather than 1/0.
  39. % opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single
  40. % numerical element will be shown without a square
  41. % bracket, unless it is the root object; if 0, square
  42. % brackets are forced for any numerical arrays.
  43. % opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson
  44. % will use the name of the passed obj variable as the
  45. % root object name; if obj is an expression and
  46. % does not have a name, 'root' will be used; if this
  47. % is set to 0 and rootname is empty, the root level
  48. % will be merged down to the lower level.
  49. % opt.JSONP [''|string]: to generate a JSONP output (JSON with padding),
  50. % for example, if opt.JSON='foo', the JSON data is
  51. % wrapped inside a function call as 'foo(...);'
  52. % opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson
  53. % back to the string form
  54. %
  55. % opt can be replaced by a list of ('param',value) pairs. The param
  56. % string is equivallent to a field in opt and is case sensitive.
  57. % output:
  58. % json: a binary string in the UBJSON format (see http://ubjson.org)
  59. %
  60. % examples:
  61. % 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],...
  62. % '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],...
  63. % 'MeshTri',[1 2 4;1 2 6;1 3 4;1 3 7;1 5 6;1 5 7;...
  64. % 2 8 4;2 8 6;3 8 4;3 8 7;5 8 6;5 8 7],...
  65. % 'MeshCreator','FangQ','MeshTitle','T6 Cube',...
  66. % 'SpecialData',[nan, inf, -inf]);
  67. % saveubjson('jsonmesh',jsonmesh)
  68. % saveubjson('jsonmesh',jsonmesh,'meshdata.ubj')
  69. %
  70. % license:
  71. % BSD, see LICENSE_BSD.txt files for details
  72. %
  73. % -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab)
  74. %
  75. if(nargin==1)
  76. varname=inputname(1);
  77. obj=rootname;
  78. if(isempty(varname))
  79. varname='root';
  80. end
  81. rootname=varname;
  82. else
  83. varname=inputname(2);
  84. end
  85. if(length(varargin)==1 && ischar(varargin{1}))
  86. opt=struct('FileName',varargin{1});
  87. else
  88. opt=varargin2struct(varargin{:});
  89. end
  90. opt.IsOctave=exist('OCTAVE_VERSION','builtin');
  91. rootisarray=0;
  92. rootlevel=1;
  93. forceroot=jsonopt('ForceRootName',0,opt);
  94. if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0)
  95. rootisarray=1;
  96. rootlevel=0;
  97. else
  98. if(isempty(rootname))
  99. rootname=varname;
  100. end
  101. end
  102. if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot)
  103. rootname='root';
  104. end
  105. json=obj2ubjson(rootname,obj,rootlevel,opt);
  106. if(~rootisarray)
  107. json=['{' json '}'];
  108. end
  109. jsonp=jsonopt('JSONP','',opt);
  110. if(~isempty(jsonp))
  111. json=[jsonp '(' json ')'];
  112. end
  113. % save to a file if FileName is set, suggested by Patrick Rapin
  114. if(~isempty(jsonopt('FileName','',opt)))
  115. fid = fopen(opt.FileName, 'wb');
  116. fwrite(fid,json);
  117. fclose(fid);
  118. end
  119. %%-------------------------------------------------------------------------
  120. function txt=obj2ubjson(name,item,level,varargin)
  121. if(iscell(item))
  122. txt=cell2ubjson(name,item,level,varargin{:});
  123. elseif(isstruct(item))
  124. txt=struct2ubjson(name,item,level,varargin{:});
  125. elseif(ischar(item))
  126. txt=str2ubjson(name,item,level,varargin{:});
  127. else
  128. txt=mat2ubjson(name,item,level,varargin{:});
  129. end
  130. %%-------------------------------------------------------------------------
  131. function txt=cell2ubjson(name,item,level,varargin)
  132. txt='';
  133. if(~iscell(item))
  134. error('input is not a cell');
  135. end
  136. dim=size(item);
  137. if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now
  138. item=reshape(item,dim(1),numel(item)/dim(1));
  139. dim=size(item);
  140. end
  141. len=numel(item); % let's handle 1D cell first
  142. if(len>1)
  143. if(~isempty(name))
  144. txt=[S_(checkname(name,varargin{:})) '[']; name='';
  145. else
  146. txt='[';
  147. end
  148. elseif(len==0)
  149. if(~isempty(name))
  150. txt=[S_(checkname(name,varargin{:})) 'Z']; name='';
  151. else
  152. txt='Z';
  153. end
  154. end
  155. for j=1:dim(2)
  156. if(dim(1)>1) txt=[txt '[']; end
  157. for i=1:dim(1)
  158. txt=[txt obj2ubjson(name,item{i,j},level+(len>1),varargin{:})];
  159. end
  160. if(dim(1)>1) txt=[txt ']']; end
  161. end
  162. if(len>1) txt=[txt ']']; end
  163. %%-------------------------------------------------------------------------
  164. function txt=struct2ubjson(name,item,level,varargin)
  165. txt='';
  166. if(~isstruct(item))
  167. error('input is not a struct');
  168. end
  169. dim=size(item);
  170. if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now
  171. item=reshape(item,dim(1),numel(item)/dim(1));
  172. dim=size(item);
  173. end
  174. len=numel(item);
  175. if(~isempty(name))
  176. if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end
  177. else
  178. if(len>1) txt='['; end
  179. end
  180. for j=1:dim(2)
  181. if(dim(1)>1) txt=[txt '[']; end
  182. for i=1:dim(1)
  183. names = fieldnames(item(i,j));
  184. if(~isempty(name) && len==1)
  185. txt=[txt S_(checkname(name,varargin{:})) '{'];
  186. else
  187. txt=[txt '{'];
  188. end
  189. if(~isempty(names))
  190. for e=1:length(names)
  191. txt=[txt obj2ubjson(names{e},getfield(item(i,j),...
  192. names{e}),level+(dim(1)>1)+1+(len>1),varargin{:})];
  193. end
  194. end
  195. txt=[txt '}'];
  196. end
  197. if(dim(1)>1) txt=[txt ']']; end
  198. end
  199. if(len>1) txt=[txt ']']; end
  200. %%-------------------------------------------------------------------------
  201. function txt=str2ubjson(name,item,level,varargin)
  202. txt='';
  203. if(~ischar(item))
  204. error('input is not a string');
  205. end
  206. item=reshape(item, max(size(item),[1 0]));
  207. len=size(item,1);
  208. if(~isempty(name))
  209. if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end
  210. else
  211. if(len>1) txt='['; end
  212. end
  213. isoct=jsonopt('IsOctave',0,varargin{:});
  214. for e=1:len
  215. val=item(e,:);
  216. if(len==1)
  217. obj=['' S_(checkname(name,varargin{:})) '' '',S_(val),''];
  218. if(isempty(name)) obj=['',S_(val),'']; end
  219. txt=[txt,'',obj];
  220. else
  221. txt=[txt,'',['',S_(val),'']];
  222. end
  223. end
  224. if(len>1) txt=[txt ']']; end
  225. %%-------------------------------------------------------------------------
  226. function txt=mat2ubjson(name,item,level,varargin)
  227. if(~isnumeric(item) && ~islogical(item))
  228. error('input is not an array');
  229. end
  230. if(length(size(item))>2 || issparse(item) || ~isreal(item) || ...
  231. isempty(item) || jsonopt('ArrayToStruct',0,varargin{:}))
  232. cid=I_(uint32(max(size(item))));
  233. if(isempty(name))
  234. txt=['{' S_('_ArrayType_'),S_(class(item)),S_('_ArraySize_'),I_a(size(item),cid(1)) ];
  235. else
  236. if(isempty(item))
  237. txt=[S_(checkname(name,varargin{:})),'Z'];
  238. return;
  239. else
  240. txt=[S_(checkname(name,varargin{:})),'{',S_('_ArrayType_'),S_(class(item)),S_('_ArraySize_'),I_a(size(item),cid(1))];
  241. end
  242. end
  243. else
  244. if(isempty(name))
  245. txt=matdata2ubjson(item,level+1,varargin{:});
  246. else
  247. if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1)
  248. numtxt=regexprep(regexprep(matdata2ubjson(item,level+1,varargin{:}),'^\[',''),']','');
  249. txt=[S_(checkname(name,varargin{:})) numtxt];
  250. else
  251. txt=[S_(checkname(name,varargin{:})),matdata2ubjson(item,level+1,varargin{:})];
  252. end
  253. end
  254. return;
  255. end
  256. if(issparse(item))
  257. [ix,iy]=find(item);
  258. data=full(item(find(item)));
  259. if(~isreal(item))
  260. data=[real(data(:)),imag(data(:))];
  261. if(size(item,1)==1)
  262. % Kludge to have data's 'transposedness' match item's.
  263. % (Necessary for complex row vector handling below.)
  264. data=data';
  265. end
  266. txt=[txt,S_('_ArrayIsComplex_'),'T'];
  267. end
  268. txt=[txt,S_('_ArrayIsSparse_'),'T'];
  269. if(size(item,1)==1)
  270. % Row vector, store only column indices.
  271. txt=[txt,S_('_ArrayData_'),...
  272. matdata2ubjson([iy(:),data'],level+2,varargin{:})];
  273. elseif(size(item,2)==1)
  274. % Column vector, store only row indices.
  275. txt=[txt,S_('_ArrayData_'),...
  276. matdata2ubjson([ix,data],level+2,varargin{:})];
  277. else
  278. % General case, store row and column indices.
  279. txt=[txt,S_('_ArrayData_'),...
  280. matdata2ubjson([ix,iy,data],level+2,varargin{:})];
  281. end
  282. else
  283. if(isreal(item))
  284. txt=[txt,S_('_ArrayData_'),...
  285. matdata2ubjson(item(:)',level+2,varargin{:})];
  286. else
  287. txt=[txt,S_('_ArrayIsComplex_'),'T'];
  288. txt=[txt,S_('_ArrayData_'),...
  289. matdata2ubjson([real(item(:)) imag(item(:))],level+2,varargin{:})];
  290. end
  291. end
  292. txt=[txt,'}'];
  293. %%-------------------------------------------------------------------------
  294. function txt=matdata2ubjson(mat,level,varargin)
  295. if(isempty(mat))
  296. txt='Z';
  297. return;
  298. end
  299. if(size(mat,1)==1)
  300. level=level-1;
  301. end
  302. type='';
  303. hasnegtive=(mat<0);
  304. if(isa(mat,'integer') || isinteger(mat) || (isfloat(mat) && all(mod(mat(:),1) == 0)))
  305. if(isempty(hasnegtive))
  306. if(max(mat(:))<=2^8)
  307. type='U';
  308. end
  309. end
  310. if(isempty(type))
  311. % todo - need to consider negative ones separately
  312. id= histc(abs(max(mat(:))),[0 2^7 2^15 2^31 2^63]);
  313. if(isempty(find(id)))
  314. error('high-precision data is not yet supported');
  315. end
  316. key='iIlL';
  317. type=key(find(id));
  318. end
  319. txt=[I_a(mat(:),type,size(mat))];
  320. elseif(islogical(mat))
  321. logicalval='FT';
  322. if(numel(mat)==1)
  323. txt=logicalval(mat+1);
  324. else
  325. txt=['[$U#' I_a(size(mat),'l') typecast(swapbytes(uint8(mat(:)')),'uint8')];
  326. end
  327. else
  328. if(numel(mat)==1)
  329. txt=['[' D_(mat) ']'];
  330. else
  331. txt=D_a(mat(:),'D',size(mat));
  332. end
  333. end
  334. %txt=regexprep(mat2str(mat),'\s+',',');
  335. %txt=regexprep(txt,';',sprintf('],['));
  336. % if(nargin>=2 && size(mat,1)>1)
  337. % txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']);
  338. % end
  339. if(any(isinf(mat(:))))
  340. txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:}));
  341. end
  342. if(any(isnan(mat(:))))
  343. txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:}));
  344. end
  345. %%-------------------------------------------------------------------------
  346. function newname=checkname(name,varargin)
  347. isunpack=jsonopt('UnpackHex',1,varargin{:});
  348. newname=name;
  349. if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once')))
  350. return
  351. end
  352. if(isunpack)
  353. isoct=jsonopt('IsOctave',0,varargin{:});
  354. if(~isoct)
  355. newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}');
  356. else
  357. pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start');
  358. pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end');
  359. if(isempty(pos)) return; end
  360. str0=name;
  361. pos0=[0 pend(:)' length(name)];
  362. newname='';
  363. for i=1:length(pos)
  364. newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))];
  365. end
  366. if(pos(end)~=length(name))
  367. newname=[newname str0(pos0(end-1)+1:pos0(end))];
  368. end
  369. end
  370. end
  371. %%-------------------------------------------------------------------------
  372. function val=S_(str)
  373. if(length(str)==1)
  374. val=['C' str];
  375. else
  376. val=['S' I_(int32(length(str))) str];
  377. end
  378. %%-------------------------------------------------------------------------
  379. function val=I_(num)
  380. if(~isinteger(num))
  381. error('input is not an integer');
  382. end
  383. if(num>=0 && num<255)
  384. val=['U' data2byte(swapbytes(cast(num,'uint8')),'uint8')];
  385. return;
  386. end
  387. key='iIlL';
  388. cid={'int8','int16','int32','int64'};
  389. for i=1:4
  390. if((num>0 && num<2^(i*8-1)) || (num<0 && num>=-2^(i*8-1)))
  391. val=[key(i) data2byte(swapbytes(cast(num,cid{i})),'uint8')];
  392. return;
  393. end
  394. end
  395. error('unsupported integer');
  396. %%-------------------------------------------------------------------------
  397. function val=D_(num)
  398. if(~isfloat(num))
  399. error('input is not a float');
  400. end
  401. if(isa(num,'single'))
  402. val=['d' data2byte(num,'uint8')];
  403. else
  404. val=['D' data2byte(num,'uint8')];
  405. end
  406. %%-------------------------------------------------------------------------
  407. function data=I_a(num,type,dim,format)
  408. id=find(ismember('iUIlL',type));
  409. if(id==0)
  410. error('unsupported integer array');
  411. end
  412. % based on UBJSON specs, all integer types are stored in big endian format
  413. if(id==1)
  414. data=data2byte(swapbytes(int8(num)),'uint8');
  415. blen=1;
  416. elseif(id==2)
  417. data=data2byte(swapbytes(uint8(num)),'uint8');
  418. blen=1;
  419. elseif(id==3)
  420. data=data2byte(swapbytes(int16(num)),'uint8');
  421. blen=2;
  422. elseif(id==4)
  423. data=data2byte(swapbytes(int32(num)),'uint8');
  424. blen=4;
  425. elseif(id==5)
  426. data=data2byte(swapbytes(int64(num)),'uint8');
  427. blen=8;
  428. end
  429. if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2))
  430. format='opt';
  431. end
  432. if((nargin<4 || strcmp(format,'opt')) && numel(num)>1)
  433. if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2))))
  434. cid=I_(uint32(max(dim)));
  435. data=['$' type '#' I_a(dim,cid(1)) data(:)'];
  436. else
  437. data=['$' type '#' I_(int32(numel(data)/blen)) data(:)'];
  438. end
  439. data=['[' data(:)'];
  440. else
  441. data=reshape(data,blen,numel(data)/blen);
  442. data(2:blen+1,:)=data;
  443. data(1,:)=type;
  444. data=data(:)';
  445. data=['[' data(:)' ']'];
  446. end
  447. %%-------------------------------------------------------------------------
  448. function data=D_a(num,type,dim,format)
  449. id=find(ismember('dD',type));
  450. if(id==0)
  451. error('unsupported float array');
  452. end
  453. if(id==1)
  454. data=data2byte(single(num),'uint8');
  455. elseif(id==2)
  456. data=data2byte(double(num),'uint8');
  457. end
  458. if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2))
  459. format='opt';
  460. end
  461. if((nargin<4 || strcmp(format,'opt')) && numel(num)>1)
  462. if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2))))
  463. cid=I_(uint32(max(dim)));
  464. data=['$' type '#' I_a(dim,cid(1)) data(:)'];
  465. else
  466. data=['$' type '#' I_(int32(numel(data)/(id*4))) data(:)'];
  467. end
  468. data=['[' data];
  469. else
  470. data=reshape(data,(id*4),length(data)/(id*4));
  471. data(2:(id*4+1),:)=data;
  472. data(1,:)=type;
  473. data=data(:)';
  474. data=['[' data(:)' ']'];
  475. end
  476. %%-------------------------------------------------------------------------
  477. function bytes=data2byte(varargin)
  478. bytes=typecast(varargin{:});
  479. bytes=bytes(:)';