Skip to content
Snippets Groups Projects
Select Git revision
  • 436d84e395f3a2df1798ecc77b75d42f6503ebe9
  • master default protected
  • v2.0
  • v1.9.8
  • v1.9
  • v1.8
  • v1.5
  • v0.5.0
  • v0.5.1
  • v0.8.0
  • v0.8.1
  • v0.9.0
  • v0.9.1
  • v0.9.8
  • v0.9.8-1
  • v0.9.9
  • v1.0-RC1
  • v1.0-RC2
  • v1.0
  • v1.1
  • v1.2
21 results

jdataencode.m

Blame
  • jdataencode.m 15.28 KiB
    function jdata=jdataencode(data, varargin)
    %
    % jdata=jdataencode(data)
    %    or
    % jdata=jdataencode(data, options)
    % jdata=jdataencode(data, 'Param1',value1, 'Param2',value2,...)
    %
    % Annotate a MATLAB struct or cell array into a JData-compliant data
    % structure as defined in the JData spec: http://github.com/fangq/jdata.
    % This encoded form servers as an intermediate format that allows unambiguous
    % storage, exchange of complex data structures and easy-to-serialize by
    % json encoders such as savejson and jsonencode (MATLAB R2016b or newer)
    %
    % This function implements the JData Specification Draft 3 (Jun. 2020)
    % see http://github.com/fangq/jdata for details
    %
    % author: Qianqian Fang (q.fang <at> neu.edu)
    %
    % input:
    %     data: a structure (array) or cell (array) to be encoded.
    %     options: (optional) a struct or Param/value pairs for user
    %              specified options (first in [.|.] is the default)
    %         AnnotateArray: [0|1] - if set to 1, convert all 1D/2D matrices 
    %              to the annotated JData array format to preserve data types;
    %              N-D (N>2), complex and sparse arrays are encoded using the
    %              annotated format by default. Please set this option to 1 if
    %              you intend to use MATLAB's jsonencode to convert to JSON.
    %         Base64: [0|1] if set to 1, _ArrayZipData_ is assumed to
    %       	       be encoded with base64 format and need to be
    %       	       decoded first. This is needed for JSON but not
    %       	       UBJSON data
    %         Prefix: ['x0x5F'|'x'] for JData files loaded via loadjson/loadubjson, the
    %                      default JData keyword prefix is 'x0x5F'; if the
    %                      json file is loaded using matlab2018's
    %                      jsondecode(), the prefix is 'x'; this function
    %                      attempts to automatically determine the prefix;
    %                      for octave, the default value is an empty string ''.
    %         UseArrayZipSize: [1|0] if set to 1, _ArrayZipSize_ will be added to 
    %       	       store the "pre-processed" data dimensions, i.e.
    %       	       the original data stored in _ArrayData_, and then flaten
    %       	       _ArrayData_ into a row vector using row-major
    %       	       order; if set to 0, a 2D _ArrayData_ will be used
    %         UseArrayShape: [0|1] if set to 1, a matrix will be tested by
    %                  to determine if it is diagonal, triangular, banded or
    %                  toeplitz, and use _ArrayShape_ to encode the matrix
    %         MapAsStruct: [0|1] if set to 1, convert containers.Map into
    %       	       struct; otherwise, keep it as map
    %         Compression: ['zlib'|'gzip','lzma','lz4','lz4hc'] - use zlib method 
    %       	       to compress data array
    %         CompressArraySize: [100|int]: only to compress an array if the  
    %       	       total element count is larger than this number.
    %         FormatVersion [2|float]: set the JSONLab output version; since
    %       	       v2.0, JSONLab uses JData specification Draft 1
    %       	       for output format, it is incompatible with all
    %       	       previous releases; if old output is desired,
    %       	       please set FormatVersion to 1.9 or earlier.
    %
    % example:
    %     jd=jdataencode(struct('a',rand(5)+1i*rand(5),'b',[],'c',sparse(5,5)))
    %
    %     encodedmat=jdataencode(single(magic(5)),'annotatearray',1,'prefix','x')
    %     jdatadecode(jsondecode(jsonencode(encodedmat)))  % serialize by jsonencode
    %     jdatadecode(loadjson(savejson('',encodedmat)))   % serialize by savejson
    %
    %     encodedtoeplitz=jdataencode(uint8(toeplitz([1,2,3,4],[1,5,6])),'usearrayshape',1,'prefix','x')
    %     jdatadecode(jsondecode(jsonencode(encodedtoeplitz)))  % serialize by jsonencode
    %     jdatadecode(loadjson(savejson('',encodedtoeplitz)))   % serialize by savejson
    %
    % license:
    %     BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details 
    %
    % -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab)
    %
    
    
    if(nargin==0)
        help jdataencode
        return;
    end
    
    opt=varargin2struct(varargin{:});
    if(isoctavemesh)
        opt.prefix=jsonopt('Prefix','',opt);
    else
        opt.prefix=jsonopt('Prefix',sprintf('x0x%X','_'+0),opt);
    end
    opt.compression=jsonopt('Compression','',opt);
    opt.nestarray=jsonopt('NestArray',0,opt);
    opt.formatversion=jsonopt('FormatVersion',2,opt);
    opt.compressarraysize=jsonopt('CompressArraySize',100,opt);
    opt.base64=jsonopt('Base64',0,opt);
    opt.mapasstruct=jsonopt('MapAsStruct',0,opt);
    opt.usearrayzipsize=jsonopt('UseArrayZipSize',1,opt);
    opt.messagepack=jsonopt('MessagePack',0,opt);
    opt.usearrayshape=jsonopt('UseArrayShape',0,opt) && exist('bandwidth');
    opt.annotatearray=jsonopt('AnnotateArray',0,opt);
    
    jdata=obj2jd(data,opt);
    
    %%-------------------------------------------------------------------------
    function newitem=obj2jd(item,varargin)
    
    if(iscell(item))
        newitem=cell2jd(item,varargin{:});
    elseif(isstruct(item))
        newitem=struct2jd(item,varargin{:});
    elseif(isnumeric(item) || islogical(item))
        newitem=mat2jd(item,varargin{:});
    elseif(ischar(item) || isa(item,'string'))
        newitem=mat2jd(item,varargin{:});
    elseif(isa(item,'containers.Map'))
        newitem=map2jd(item,varargin{:});
    elseif(isa(item,'categorical'))
        newitem=cell2jd(cellstr(item),varargin{:});
    elseif(isa(item,'function_handle'))
        newitem=struct2jd(functions(item),varargin{:});
    elseif(isa(item,'table'))
        newitem=table2jd(item,varargin{:});
    elseif(isa(item,'digraph') || isa(item,'graph'))
        newitem=graph2jd(item,varargin{:});
    elseif(isobject(item))
        newitem=matlabobject2jd(item,varargin{:});
    else
        newitem=item;
    end
    
    %%-------------------------------------------------------------------------
    function newitem=cell2jd(item,varargin)
    
    newitem=cellfun(@(x) obj2jd(x, varargin{:}), item, 'UniformOutput',false);
    
    %%-------------------------------------------------------------------------
    function newitem=struct2jd(item,varargin)
    
    num=numel(item);
    if(num>1)  % struct array
        newitem=obj2jd(num2cell(item),varargin{:});
        try
           newitem=cell2mat(newitem);
        catch
        end
    else       % a single struct
        names=fieldnames(item);
        newitem=struct;
        for i=1:length(names)
            newitem.(names{i})=obj2jd(item.(names{i}),varargin{:});
        end
    end
    
    %%-------------------------------------------------------------------------
    function newitem=map2jd(item,varargin)
    
    names=item.keys;
    if(varargin{1}.mapasstruct)  % convert a map to struct
        newitem=struct;
        if(~strcmp(item.KeyType,'char'))
            data=num2cell(reshape([names, item.values],length(names),2),2);
            for i=1:length(names)
                data{i}{2}=obj2jd(data{i}{2},varargin{:});
            end
            newitem.(N_('_MapData_',varargin{:}))=data;
        else
            for i=1:length(names)
                newitem.(N_(names{i},varargin{:}))=obj2jd(item(names{i}),varargin{:});
            end
        end
    else   % keep as a map and only encode its values
        if(strcmp(item.KeyType,'char'))
            newitem=containers.Map();
        else
            newitem=containers.Map('KeyType',item.KeyType,'ValueType','any');
        end
        for i=1:length(names)
            newitem(names{i})=obj2jd(item(names{i}),varargin{:});
        end
    end
    %%-------------------------------------------------------------------------
    function newitem=mat2jd(item,varargin)
    
    N=@(x) N_(x,varargin{:});
    newitem=struct(N('_ArrayType_'),class(item),N('_ArraySize_'),size(item));
    
    zipmethod=varargin{1}.compression;
    minsize=varargin{1}.compressarraysize;
    
    % 2d numerical (real/complex/sparse) arrays with _ArrayShape_ encoding enabled
    if(varargin{1}.usearrayshape && ndims(item)==2 && ~isvector(item))
        encoded=1;
        if(~isreal(item))
            newitem.(N('_ArrayIsComplex_'))=true;
        end
        symmtag='';
        if(isreal(item) && issymmetric(double(item)))
            symmtag='symm';
            item=tril(item);
        elseif(~isreal(item) && ishermitian(double(item)))
            symmtag='herm';
            item=tril(item);
        end
        [lband,uband]=bandwidth(double(item));
        newitem.(N('_ArrayZipSize_'))=[lband+uband+1, min(size(item,1),size(item,2))];
        if(lband+uband==0) % isdiag
            newitem.(N('_ArrayShape_'))='diag';
            newitem.(N('_ArrayData_'))=diag(item).';
        elseif(uband==0 && lband==size(item,1)-1) % lower triangular
            newitem.(N('_ArrayShape_'))=['lower' symmtag];
            item=item.';
            newitem.(N('_ArrayData_'))=item(triu(true(size(item)))).';
        elseif(lband==0 && uband==size(item,2)-1) % upper triangular
            newitem.(N('_ArrayShape_'))='upper';
            item=item.';
            newitem.(N('_ArrayData_'))=item(tril(true(size(item)))).';
        elseif(lband==0) % upper band
            newitem.(N('_ArrayShape_'))={'upperband',uband};
            newitem.(N('_ArrayData_'))=spdiags(item.',-uband:lband).';
        elseif(uband==0) % lower band
            newitem.(N('_ArrayShape_'))={sprintf('lower%sband',symmtag),lband};
            newitem.(N('_ArrayData_'))=spdiags(item.',-uband:lband).';
        elseif(uband<size(item,2)-1 || lband<size(item,1)-1) % band
            newitem.(N('_ArrayShape_'))={'band',uband,lband};
            newitem.(N('_ArrayData_'))=spdiags(item.',-uband:lband).';
        elseif(all(toeplitz(item(:,1),item(1,:))==item))  % Toeplitz matrix
            newitem.(N('_ArrayShape_'))='toeplitz';
            newitem.(N('_ArrayZipSize_'))=[2,max(size(item))];
            newitem.(N('_ArrayData_'))=zeros(2,max(size(item)));
            newitem.(N('_ArrayData_'))(1,1:size(item,2))=item(1,:);
            newitem.(N('_ArrayData_'))(2,1:size(item,1))=item(:,1).';
        else  % full matrix
            newitem=rmfield(newitem,N('_ArrayZipSize_'));
            encoded=0;
        end
    
        % serialize complex data at last
        if(encoded && isstruct(newitem) && ~isreal(newitem.(N('_ArrayData_'))))
            item=squeeze(zeros([2, size(newitem.(N('_ArrayData_')))]));
            item(1,:)=real(newitem.(N('_ArrayData_'))(:));
            item(2,:)=imag(newitem.(N('_ArrayData_'))(:));
            newitem.(N('_ArrayZipSize_'))=size(item);
            newitem.(N('_ArrayData_'))=item;
        end
    
        % wrap _ArrayData_ into a single row vector, and store preprocessed
        % size to _ArrayZipSize_ (force varargin{1}.usearrayzipsize=true)
        if(encoded)
            if(isstruct(newitem) && ~isvector(newitem.(N('_ArrayData_'))))
                item=newitem.(N('_ArrayData_'));
                item=permute(item,ndims(item):-1:1);
                newitem.(N('_ArrayData_'))=item(:).';
            else
                newitem=rmfield(newitem,N('_ArrayZipSize_'));
            end
            newitem.(N('_ArrayData_'))=full(newitem.(N('_ArrayData_')));
            return
        end
    end
    
    % no encoding for char arrays or non-sparse real vectors
    if(isempty(item) || isa(item,'string') || ischar(item) || varargin{1}.nestarray || ...
            ((isvector(item) || ndims(item)==2) && isreal(item) && ~issparse(item) && ...
            ~varargin{1}.annotatearray)) 
        newitem=item;
        return;
    end
    
    if(isa(item,'logical'))
        item=uint8(item);
    end
    
    if(isreal(item))
        if(issparse(item))
            fulldata=full(item(item~=0));
            newitem.(N('_ArrayIsSparse_'))=true;
            newitem.(N('_ArrayZipSize_'))=[2+(~isvector(item)),length(fulldata)];
            if(isvector(item))
                newitem.(N('_ArrayData_'))=[find(item(:))', fulldata(:)'];
            else
                [ix,iy]=find(item);
                    newitem.(N('_ArrayData_'))=[ix(:)' , iy(:)', fulldata(:)'];
            end
        else
            if(varargin{1}.formatversion>1.9)
                    item=permute(item,ndims(item):-1:1);
            end
            newitem.(N('_ArrayData_'))=item(:)';
        end
    else
        newitem.(N('_ArrayIsComplex_'))=true;
        if(issparse(item))
            fulldata=full(item(item~=0));
            newitem.(N('_ArrayIsSparse_'))=true;
            newitem.(N('_ArrayZipSize_'))=[3+(~isvector(item)),length(fulldata)];
            if(isvector(item))
                newitem.(N('_ArrayData_'))=[find(item(:))', real(fulldata(:))', imag(fulldata(:))'];
            else
                [ix,iy]=find(item);
                newitem.(N('_ArrayData_'))=[ix(:)' , iy(:)' , real(fulldata(:))', imag(fulldata(:))'];
            end
        else
            if(varargin{1}.formatversion>1.9)
                    item=permute(item,ndims(item):-1:1);
            end
            newitem.(N('_ArrayZipSize_'))=[2,numel(item)];
            newitem.(N('_ArrayData_'))=[real(item(:))', imag(item(:))'];
        end
    end
    
    if(varargin{1}.usearrayzipsize==0 && isfield(newitem,N('_ArrayZipSize_')))
        data=newitem.(N('_ArrayData_'));
        data=reshape(data,fliplr(newitem.(N('_ArrayZipSize_'))));
        newitem.(N('_ArrayData_'))=permute(data,ndims(data):-1:1);
        newitem=rmfield(newitem,N('_ArrayZipSize_'));
    end
    
    if(~isempty(zipmethod) && numel(item)>minsize)
        compfun=str2func([zipmethod 'encode']);
        newitem.(N('_ArrayZipType_'))=lower(zipmethod);
        if(~isfield(newitem,N('_ArrayZipSize_')))
            newitem.(N('_ArrayZipSize_'))=size(newitem.(N('_ArrayData_')));
        end
        newitem.(N('_ArrayZipData_'))=compfun(typecast(newitem.(N('_ArrayData_'))(:).','uint8'));
        newitem=rmfield(newitem,N('_ArrayData_'));
        if(varargin{1}.base64)
            newitem.(N('_ArrayZipData_'))=char(base64encode(newitem.(N('_ArrayZipData_'))));
        end
    end
    
    if(isfield(newitem,N('_ArrayData_')) && isempty(newitem.(N('_ArrayData_'))))
        newitem.(N('_ArrayData_'))=[];
    end
    
    %%-------------------------------------------------------------------------
    function newitem=table2jd(item,varargin)
    
    newitem=struct;
    newitem.(N_('_TableCols_',varargin{:}))=item.Properties.VariableNames;
    newitem.(N_('_TableRows_',varargin{:}))=item.Properties.RowNames';
    newitem.(N_('_TableRecords_',varargin{:}))=table2cell(item);
    
    %%-------------------------------------------------------------------------
    function newitem=graph2jd(item,varargin)
    
    newitem=struct;
    nodedata=table2struct(item.Nodes);
    if(isfield(nodedata,'Name'))
        nodedata=rmfield(nodedata,'Name');
        newitem.(N_('_GraphNodes_',varargin{:}))=containers.Map(item.Nodes.Name,num2cell(nodedata),'UniformValues',false);
    else
        newitem.(N_('_GraphNodes_',varargin{:}))=containers.Map(1:max(item.Edges.EndNodes(:)),num2cell(nodedata),'UniformValues',false);
    end
    edgenodes=num2cell(item.Edges.EndNodes);
    edgedata=table2struct(item.Edges);
    if(isfield(edgedata,'EndNodes'))
        edgedata=rmfield(edgedata,'EndNodes');
    end
    edgenodes(:,3)=num2cell(edgedata);
    if(isa(item,'graph'))
        if(strcmp(varargin{1}.prefix,'x'))
            newitem.(genvarname('_GraphEdges0_'))=edgenodes;
        else
            newitem.(encodevarname('_GraphEdges0_'))=edgenodes;
        end
    else
        newitem.(N_('_GraphEdges_',varargin{:}))=edgenodes;
    end
    
    %%-------------------------------------------------------------------------
    function newitem=matlabobject2jd(item,varargin)
    try
        if numel(item) == 0 %empty object
            newitem = struct();
        elseif numel(item) == 1 %
            newitem = char(item);
        else
            propertynames = properties(item);
            for p = 1:numel(propertynames)
                for o = numel(item):-1:1 % aray of objects
                    newitem(o).(propertynames{p}) = item(o).(propertynames{p});
                end
            end
        end
    catch
        newitem=any2jd(item,varargin{:});
    end
    
    %%-------------------------------------------------------------------------
    function newitem=any2jd(item,varargin)
    
    N=@(x) N_(x,varargin{:});
    newitem.(N('_DataInfo_'))=struct('MATLABObjectClass',class(item),'MATLABObjectSize',size(item));
    newitem.(N('_ByteStream_'))=getByteStreamFromArray(item);  % use undocumented matlab function
    if(varargin{1}.base64)
        newitem.(N('_ByteStream_'))=char(base64encode(newitem.(N('_ByteStream_'))));
    end
    
    %%-------------------------------------------------------------------------
    function newname=N_(name,varargin)
    
    newname=[varargin{1}.prefix name];