function json=saveubjson(rootname,obj,varargin) import jsonlab.* % % json=saveubjson(rootname,obj,filename) % or % json=saveubjson(rootname,obj,opt) % json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...) % % convert a MATLAB object (cell, struct or array) into a Universal % Binary JSON (UBJSON) binary string % % author: Qianqian Fang (q.fang neu.edu) % created on 2013/08/17 % % $Id$ % % input: % rootname: the name of the root-object, when set to '', the root name % is ignored, however, when opt.ForceRootName is set to 1 (see below), % the MATLAB variable name will be used as the root name. % obj: a MATLAB object (array, cell, cell array, struct, struct array, % class instance) % filename: a string for the file name to save the output UBJSON data % opt: a struct for additional options, ignore to use default values. % opt can have the following fields (first in [.|.] is the default) % % opt.FileName [''|string]: a file name to save the output JSON data % opt.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D % array in JSON array format; if sets to 1, an % array will be shown as a struct with fields % "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for % sparse arrays, the non-zero elements will be % saved to _ArrayData_ field in triplet-format i.e. % (ix,iy,val) and "_ArrayIsSparse_" will be added % with a value of 1; for a complex array, the % _ArrayData_ array will include two columns % (4 for sparse) to record the real and imaginary % parts, and also "_ArrayIsComplex_":1 is added. % opt.ParseLogical [1|0]: if this is set to 1, logical array elem % will use true/false rather than 1/0. % opt.SingletArray [0|1]: if this is set to 1, arrays with a single % numerical element will be shown without a square % bracket, unless it is the root object; if 0, square % brackets are forced for any numerical arrays. % opt.SingletCell [1|0]: if 1, always enclose a cell with "[]" % even it has only one element; if 0, brackets % are ignored when a cell has only 1 element. % opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson % will use the name of the passed obj variable as the % root object name; if obj is an expression and % does not have a name, 'root' will be used; if this % is set to 0 and rootname is empty, the root level % will be merged down to the lower level. % opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), % for example, if opt.JSON='foo', the JSON data is % wrapped inside a function call as 'foo(...);' % opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson % back to the string form % opt.Compression 'zlib' or 'gzip': specify array compression % method; currently only supports 'gzip' or 'zlib'. The % data compression only applicable to numerical arrays % in 3D or higher dimensions, or when ArrayToStruct % is 1 for 1D or 2D arrays. If one wants to % compress a long string, one must convert % it to uint8 or int8 array first. The compressed % array uses three extra fields % "_ArrayCompressionMethod_": the opt.Compression value. % "_ArrayCompressionSize_": a 1D interger array to % store the pre-compressed (but post-processed) % array dimensions, and % "_ArrayCompressedData_": the binary stream of % the compressed binary array data WITHOUT % 'base64' encoding % opt.CompressArraySize [100|int]: only to compress an array if the total % element count is larger than this number. % % opt can be replaced by a list of ('param',value) pairs. The param % string is equivallent to a field in opt and is case sensitive. % output: % json: a binary string in the UBJSON format (see http://ubjson.org) % % examples: % 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],... % '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],... % 'MeshTri',[1 2 4;1 2 6;1 3 4;1 3 7;1 5 6;1 5 7;... % 2 8 4;2 8 6;3 8 4;3 8 7;5 8 6;5 8 7],... % 'MeshCreator','FangQ','MeshTitle','T6 Cube',... % 'SpecialData',[nan, inf, -inf]); % saveubjson('jsonmesh',jsonmesh) % saveubjson('jsonmesh',jsonmesh,'meshdata.ubj') % % 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) % if(nargin==1) varname=inputname(1); obj=rootname; if(isempty(varname)) varname='root'; end rootname=varname; else varname=inputname(2); end if(length(varargin)==1 && ischar(varargin{1})) opt=struct('filename',varargin{1}); else opt=varargin2struct(varargin{:}); end opt.IsOctave=exist('OCTAVE_VERSION','builtin'); dozip=jsonopt('Compression','',opt); if(~isempty(dozip)) if(~(strcmpi(dozip,'gzip') || strcmpi(dozip,'zlib'))) error('compression method "%s" is not supported',dozip); end if(exist('zmat')~=3) try error(javachk('jvm')); try base64decode('test'); catch matlab.net.base64decode('test'); end catch error('java-based compression is not supported'); end end opt.Compression=dozip; end if(isfield(opt,'norowbracket')) warning('Option ''NoRowBracket'' is depreciated, please use ''SingletArray'' and set its value to not(NoRowBracket)'); if(~isfield(opt,'singletarray')) opt.singletarray=not(opt.norowbracket); end end rootisarray=0; rootlevel=1; forceroot=jsonopt('ForceRootName',0,opt); if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || ... iscell(obj) || isobject(obj)) && isempty(rootname) && forceroot==0) rootisarray=1; rootlevel=0; else if(isempty(rootname)) rootname=varname; end end if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) rootname='root'; end json=obj2ubjson(rootname,obj,rootlevel,opt); if(~rootisarray) json=['{' json '}']; end jsonp=jsonopt('JSONP','',opt); if(~isempty(jsonp)) json=[jsonp '(' json ')']; end % save to a file if FileName is set, suggested by Patrick Rapin filename=jsonopt('FileName','',opt); if(~isempty(filename)) fid = fopen(filename, 'wb'); fwrite(fid,json); fclose(fid); end %%------------------------------------------------------------------------- function txt=obj2ubjson(name,item,level,varargin) import jsonlab.* if(iscell(item)) txt=cell2ubjson(name,item,level,varargin{:}); elseif(isstruct(item)) txt=struct2ubjson(name,item,level,varargin{:}); elseif(ischar(item)) txt=str2ubjson(name,item,level,varargin{:}); elseif(isobject(item)) txt=matlabobject2ubjson(name,item,level,varargin{:}); else txt=mat2ubjson(name,item,level,varargin{:}); end %%------------------------------------------------------------------------- function txt=cell2ubjson(name,item,level,varargin) import jsonlab.* txt=''; if(~iscell(item)) error('input is not a cell'); end dim=size(item); if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now item=reshape(item,dim(1),numel(item)/dim(1)); dim=size(item); end bracketlevel=~jsonopt('singletcell',1,varargin{:}); len=numel(item); % let's handle 1D cell first if(len>bracketlevel) if(~isempty(name)) txt=[N_(checkname(name,varargin{:})) '[']; name=''; else txt='['; end elseif(len==0) if(~isempty(name)) txt=[N_(checkname(name,varargin{:})) 'Z']; name=''; else txt='Z'; end end for j=1:dim(2) if(dim(1)>1) txt=[txt '[']; end for i=1:dim(1) txt=[txt obj2ubjson(name,item{i,j},level+(len>bracketlevel),varargin{:})]; end if(dim(1)>1) txt=[txt ']']; end end if(len>bracketlevel) txt=[txt ']']; end %%------------------------------------------------------------------------- function txt=struct2ubjson(name,item,level,varargin) import jsonlab.* txt=''; if(~isstruct(item)) error('input is not a struct'); end dim=size(item); if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now item=reshape(item,dim(1),numel(item)/dim(1)); dim=size(item); end len=numel(item); forcearray= (len>1 || (jsonopt('SingletArray',0,varargin{:})==1 && level>0)); if(~isempty(name)) if(forcearray) txt=[N_(checkname(name,varargin{:})) '[']; end else if(forcearray) txt='['; end end for j=1:dim(2) if(dim(1)>1) txt=[txt '[']; end for i=1:dim(1) names = fieldnames(item(i,j)); if(~isempty(name) && len==1 && ~forcearray) txt=[txt N_(checkname(name,varargin{:})) '{']; else txt=[txt '{']; end if(~isempty(names)) for e=1:length(names) txt=[txt obj2ubjson(names{e},item(i,j).(names{e}),... level+(dim(1)>1)+1+forcearray,varargin{:})]; end end txt=[txt '}']; end if(dim(1)>1) txt=[txt ']']; end end if(forcearray) txt=[txt ']']; end %%------------------------------------------------------------------------- function txt=str2ubjson(name,item,level,varargin) import jsonlab.* txt=''; if(~ischar(item)) error('input is not a string'); end item=reshape(item, max(size(item),[1 0])); len=size(item,1); if(~isempty(name)) if(len>1) txt=[N_(checkname(name,varargin{:})) '[']; end else if(len>1) txt='['; end end for e=1:len val=item(e,:); if(len==1) obj=[N_(checkname(name,varargin{:})) '' '',S_(val),'']; if(isempty(name)) obj=['',S_(val),'']; end txt=[txt,'',obj]; else txt=[txt,'',['',S_(val),'']]; end end if(len>1) txt=[txt ']']; end %%------------------------------------------------------------------------- function txt=mat2ubjson(name,item,level,varargin) import jsonlab.* if(~isnumeric(item) && ~islogical(item)) error('input is not an array'); end dozip=jsonopt('Compression','',varargin{:}); zipsize=jsonopt('CompressArraySize',100,varargin{:}); if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... (isempty(item) && any(size(item))) ||jsonopt('ArrayToStruct',0,varargin{:}) || (~isempty(dozip) && numel(item)>zipsize)) cid=I_(uint32(max(size(item)))); if(isempty(name)) txt=['{' N_('_ArrayType_'),S_(class(item)),N_('_ArraySize_'),I_a(size(item),cid(1)) ]; else if(isempty(item)) txt=[N_(checkname(name,varargin{:})),'Z']; return; else txt=[N_(checkname(name,varargin{:})),'{',N_('_ArrayType_'),S_(class(item)),N_('_ArraySize_'),I_a(size(item),cid(1))]; end end else if(isempty(name)) txt=matdata2ubjson(item,level+1,varargin{:}); else if(numel(item)==1 && jsonopt('SingletArray',0,varargin{:})==0) numtxt=regexprep(regexprep(matdata2ubjson(item,level+1,varargin{:}),'^\[',''),']$',''); txt=[N_(checkname(name,varargin{:})) numtxt]; else txt=[N_(checkname(name,varargin{:})),matdata2ubjson(item,level+1,varargin{:})]; end end return; end if(issparse(item)) [ix,iy]=find(item); data=full(item(find(item))); if(~isreal(item)) data=[real(data(:)),imag(data(:))]; if(size(item,1)==1) % Kludge to have data's 'transposedness' match item's. % (Necessary for complex row vector handling below.) data=data'; end txt=[txt,N_('_ArrayIsComplex_'),'T']; end txt=[txt,N_('_ArrayIsSparse_'),'T']; if(~isempty(dozip) && numel(data*2)>zipsize) if(size(item,1)==1) % Row vector, store only column indices. fulldata=[iy(:),data']; elseif(size(item,2)==1) % Column vector, store only row indices. fulldata=[ix,data]; else % General case, store row and column indices. fulldata=[ix,iy,data]; end cid=I_(uint32(max(size(fulldata)))); txt=[txt, N_('_ArrayCompressionSize_'),I_a(size(fulldata),cid(1))]; txt=[txt, N_('_ArrayCompressionMethod_'),S_(dozip)]; if(strcmpi(dozip,'gzip')) txt=[txt,N_('_ArrayCompressedData_'), I_a(gzipencode(typecast(fulldata(:),'uint8')),'U')]; elseif(strcmpi(dozip,'zlib')) txt=[txt,N_('_ArrayCompressedData_'), I_a(zlibencode(typecast(fulldata(:),'uint8')),'U')]; else error('compression method not supported'); end else if(size(item,1)==1) % Row vector, store only column indices. txt=[txt,N_('_ArrayData_'),... matdata2ubjson([iy(:),data'],level+2,varargin{:})]; elseif(size(item,2)==1) % Column vector, store only row indices. txt=[txt,N_('_ArrayData_'),... matdata2ubjson([ix,data],level+2,varargin{:})]; else % General case, store row and column indices. txt=[txt,N_('_ArrayData_'),... matdata2ubjson([ix,iy,data],level+2,varargin{:})]; end end else if(~isempty(dozip) && numel(item)>zipsize) if(isreal(item)) fulldata=item(:)'; if(islogical(fulldata)) fulldata=uint8(fulldata); end else txt=[txt,N_('_ArrayIsComplex_'),'T']; fulldata=[real(item(:)) imag(item(:))]; end cid=I_(uint32(max(size(fulldata)))); txt=[txt, N_('_ArrayCompressionSize_'),I_a(size(fulldata),cid(1))]; txt=[txt, N_('_ArrayCompressionMethod_'),S_(dozip)]; if(strcmpi(dozip,'gzip')) txt=[txt,N_('_ArrayCompressedData_'), I_a(gzipencode(typecast(fulldata(:),'uint8')),'U')]; elseif(strcmpi(dozip,'zlib')) txt=[txt,N_('_ArrayCompressedData_'), I_a(zlibencode(typecast(fulldata(:),'uint8')),'U')]; else error('compression method not supported'); end else if(isreal(item)) txt=[txt,N_('_ArrayData_'),... matdata2ubjson(item(:)',level+2,varargin{:})]; else txt=[txt,N_('_ArrayIsComplex_'),'T']; txt=[txt,N_('_ArrayData_'),... matdata2ubjson([real(item(:)) imag(item(:))],level+2,varargin{:})]; end end end txt=[txt,'}']; %%------------------------------------------------------------------------- function txt=matlabobject2ubjson(name,item,level,varargin) import jsonlab.* st = struct(); if numel(item) > 0 %non-empty object % "st = struct(item);" would produce an inmutable warning, because it % make the protected and private properties visible. Instead we get the % visible properties propertynames = properties(item); for p = 1:numel(propertynames) for o = numel(item):-1:1 % aray of objects st(o).(propertynames{p}) = item(o).(propertynames{p}); end end end txt=struct2ubjson(name,st,level,varargin{:}); %%------------------------------------------------------------------------- function txt=matdata2ubjson(mat,level,varargin) import jsonlab.* if(isempty(mat)) txt='Z'; return; end type=''; hasnegtive=(mat<0); if(isa(mat,'integer') || isinteger(mat) || (isfloat(mat) && all(mod(mat(:),1) == 0))) if(isempty(hasnegtive)) if(max(mat(:))<=2^8) type='U'; end end if(isempty(type)) % todo - need to consider negative ones separately id= histc(abs(max(double(mat(:)))),[0 2^7 2^15 2^31 2^63]); if(isempty(id~=0)) error('high-precision data is not yet supported'); end key='iIlL'; type=key(id~=0); end txt=[I_a(mat(:),type,size(mat))]; elseif(islogical(mat)) logicalval='FT'; if(numel(mat)==1) txt=logicalval(mat+1); else txt=['[$U#' I_a(size(mat),'l') typecast(swapbytes(uint8(mat(:)')),'uint8')]; end else if(numel(mat)==1) txt=['[' D_(mat) ']']; else txt=D_a(mat(:),'D',size(mat)); end end %txt=regexprep(mat2str(mat),'\s+',','); %txt=regexprep(txt,';',sprintf('],[')); % if(nargin>=2 && size(mat,1)>1) % txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); % end if(any(isinf(mat(:)))) txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); end if(any(isnan(mat(:)))) txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); end %%------------------------------------------------------------------------- function newname=checkname(name,varargin) import jsonlab.* isunpack=jsonopt('UnpackHex',1,varargin{:}); newname=name; if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) return end if(isunpack) isoct=jsonopt('IsOctave',0,varargin{:}); if(~isoct) newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); else pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); if(isempty(pos)) return; end str0=name; pos0=[0 pend(:)' length(name)]; newname=''; for i=1:length(pos) newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; end if(pos(end)~=length(name)) newname=[newname str0(pos0(end-1)+1:pos0(end))]; end end end %%------------------------------------------------------------------------- function val=N_(str) import jsonlab.* val=[I_(int32(length(str))) str]; %%------------------------------------------------------------------------- function val=S_(str) import jsonlab.* if(length(str)==1) val=['C' str]; else val=['S' I_(int32(length(str))) str]; end %%------------------------------------------------------------------------- function val=I_(num) import jsonlab.* if(~isinteger(num)) error('input is not an integer'); end if(num>=0 && num<255) val=['U' data2byte(swapbytes(cast(num,'uint8')),'uint8')]; return; end key='iIlL'; cid={'int8','int16','int32','int64'}; for i=1:4 if((num>0 && num<2^(i*8-1)) || (num<0 && num>=-2^(i*8-1))) val=[key(i) data2byte(swapbytes(cast(num,cid{i})),'uint8')]; return; end end error('unsupported integer'); %%------------------------------------------------------------------------- function val=D_(num) import jsonlab.* if(~isfloat(num)) error('input is not a float'); end if(isa(num,'single')) val=['d' data2byte(swapbytes(num),'uint8')]; else val=['D' data2byte(swapbytes(num),'uint8')]; end %%------------------------------------------------------------------------- function data=I_a(num,type,dim,format) import jsonlab.* id=find(ismember('iUIlL',type)); if(id==0) error('unsupported integer array'); end % based on UBJSON specs, all integer types are stored in big endian format if(id==1) data=data2byte(swapbytes(int8(num)),'uint8'); blen=1; elseif(id==2) data=data2byte(swapbytes(uint8(num)),'uint8'); blen=1; elseif(id==3) data=data2byte(swapbytes(int16(num)),'uint8'); blen=2; elseif(id==4) data=data2byte(swapbytes(int32(num)),'uint8'); blen=4; elseif(id==5) data=data2byte(swapbytes(int64(num)),'uint8'); blen=8; end if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) format='opt'; end if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) cid=I_(uint32(max(dim))); data=['$' type '#' I_a(dim,cid(1)) data(:)']; else data=['$' type '#' I_(int32(numel(data)/blen)) data(:)']; end data=['[' data(:)']; else data=reshape(data,blen,numel(data)/blen); data(2:blen+1,:)=data; data(1,:)=type; data=data(:)'; data=['[' data(:)' ']']; end %%------------------------------------------------------------------------- function data=D_a(num,type,dim,format) import jsonlab.* id=find(ismember('dD',type)); if(id==0) error('unsupported float array'); end if(id==1) data=data2byte(swapbytes(single(num)),'uint8'); elseif(id==2) data=data2byte(swapbytes(double(num)),'uint8'); end if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) format='opt'; end if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) cid=I_(uint32(max(dim))); data=['$' type '#' I_a(dim,cid(1)) data(:)']; else data=['$' type '#' I_(int32(numel(data)/(id*4))) data(:)']; end data=['[' data]; else data=reshape(data,(id*4),length(data)/(id*4)); data(2:(id*4+1),:)=data; data(1,:)=type; data=data(:)'; data=['[' data(:)' ']']; end %%------------------------------------------------------------------------- function bytes=data2byte(varargin) import jsonlab.* bytes=typecast(varargin{:}); bytes=char(bytes(:)');