From 3dfa904faec470c66b39bdc522a9d149984a7908 Mon Sep 17 00:00:00 2001
From: Qianqian Fang <fangqq@gmail.com>
Date: Sun, 13 Feb 2022 23:53:42 -0500
Subject: [PATCH] debugged and tested mmap, add mmapinclude and mmapexclude
 options

---
 filterjsonmmap.m        | 53 +++++++++++++++++++++++++++
 jsonget.m               | 53 +++++++++++++++------------
 jsonset.m               | 40 +++++++++++++--------
 loadbj.m                | 62 +++++++++++++++++++-------------
 loadjson.m              | 80 +++++++++++++++++++++++++++--------------
 savejson.m              |  2 +-
 test/run_jsonlab_test.m | 48 ++++++++++++++++++++++++-
 7 files changed, 249 insertions(+), 89 deletions(-)
 create mode 100644 filterjsonmmap.m

diff --git a/filterjsonmmap.m b/filterjsonmmap.m
new file mode 100644
index 0000000..1070e10
--- /dev/null
+++ b/filterjsonmmap.m
@@ -0,0 +1,53 @@
+function mmap=filterjsonmmap(mmap, patterns, isinclude)
+%
+% mmap=filterjsonmmap(mmap, patterns, isinclude)
+%
+% filter JSON mmap keys based on inclusive or exclusive string patterns
+%
+% authors:Qianqian Fang (q.fang <at> neu.edu)
+% initially created on 2022/02/13
+%
+% input:
+%      mmap: memory-map returned by loadjson/loadbj of the same data
+%            important: mmap must be produced from the same file/string,
+%            otherwise calling this function may cause data corruption
+%      patterns: a string or a cell array of strings, each string will 
+%            be tested to match the JSONPath keys in mmap
+%      isinclude: 1 (default) to include all mmap entries that match at
+%            least one of the patterns, and 0 - exclude those that match
+%
+% output:
+%      mmap: a filtered JSON mmap
+%
+% examples:
+%      str='{"arr":[[1,2],"a",{"c":2}],"obj":{"k":"test"}}';
+%      [dat, mmap]=loadjson(str);
+%      savejson('',mmap)
+%      newmmap=filterjsonmmap(mmap,{'arr.[1]', 'obj.k'});
+%      savejson('',newmmap)
+%
+% 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<3)
+        isinclude=1;
+    end
+    if(nargin>1 && ~isempty(patterns))
+        keylist=[mmap{:}];
+        keylist=keylist(1:2:end);
+        if(~iscell(patterns))
+            patterns={patterns};
+        end
+        mask=zeros(1,length(keylist));
+        for i=1:length(patterns)
+            mask=mask+cellfun(@length, strfind(keylist,patterns{i}));
+        end
+        if(isinclude)
+            mmap=mmap(mask>0);
+        else
+            mmap(mask>0)=[];
+        end
+    end
+end
\ No newline at end of file
diff --git a/jsonget.m b/jsonget.m
index 1f5d921..3e0a8e4 100644
--- a/jsonget.m
+++ b/jsonget.m
@@ -14,7 +14,8 @@ function json=jsonget(fname,mmap,varargin)
 %            important: mmap must be produced from the same file/string,
 %            otherwise calling this function may cause data corruption
 %      '$.jsonpath1,2,3,...':  a series of strings in the form of JSONPath
-%            as the key to each of the record to be retrieved
+%            as the key to each of the record to be retrieved; if no paths
+%            are given, all items in mmap are retrieved
 %
 % output:
 %      json: a cell array, made of elements {'$.jsonpath_i',json_string_i}
@@ -23,8 +24,8 @@ function json=jsonget(fname,mmap,varargin)
 %      str='[[1,2],"a",{"c":2}]{"k":"test"}';
 %      [dat, mmap]=loadjson(str);
 %      savejson('',dat,'filename','mydata.json','compact',1);
-%      json=jsonget(str,mmap,'$.[0].[*]','$.[2].c')
-%      json=jsonget('mydata.json',mmap,'$.[0].[*]','$.[2].c')
+%      json=jsonget(str,mmap,'$.[0]','$.[2].c')
+%      json=jsonget('mydata.json',mmap,'$.[0]','$.[2].c')
 %
 % license:
 %     BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details 
@@ -34,18 +35,11 @@ function json=jsonget(fname,mmap,varargin)
 
 if(regexp(fname,'^\s*(?:\[.*\])|(?:\{.*\})\s*$','once'))
     inputstr=fname;
-elseif(isoctavemesh)
+elseif(~exist('memmapfile','file'))
     if(exist(fname,'file'))
        try
            fid = fopen(fname,'rb');
-           inputstr = fread(fid,'char',inf)';
-           fclose(fid);
        catch
-           try
-               inputstr = urlread(['file://',fname]);
-           catch
-               inputstr = urlread(['file://',fullfile(pwd,fname)]);
-           end
        end
     end
 end
@@ -53,18 +47,33 @@ end
 mmap=[mmap{:}];
 keylist=mmap(1:2:end);
 
+loc=1:length(keylist);
+if(length(varargin)>=1)
+    [tf,loc]=ismember(varargin,keylist);
+    if(any(tf))
+       keylist=keylist(loc);
+    else
+       keylist={};
+    end
+end
+
 json={};
-for i=1:length(varargin)
-    if(regexp(varargin{i},'^\$'))
-        [tf,loc]=ismember(varargin{i},keylist);
-        if(tf)
-            rec={'uint8',[1,mmap{loc*2}(2)],  'x'};
-            if(exist('inputstr','var'))
-                json{end+1}={varargin{i}, inputstr(mmap{loc*2}(1):mmap{loc*2}(1)+mmap{loc*2}(2)-1)};
-            else
-                fmap=memmapfile(fname,'writable',false, 'offset',mmap{loc*2}(1),'format', rec);
-                json{end+1}={varargin{i}, char(fmap.Data(1).x)};
-            end
+for i=1:length(keylist)
+    bmap=mmap{loc(i)*2};
+    rec={'uint8',[1,bmap(2)],  'x'};
+    if(exist('inputstr','var'))
+        json{end+1}={keylist{i}, inputstr(bmap(1):bmap(1)+bmap(2)-1)};
+    else
+        if(exist('fid','var') && fid>=0)
+            fseek(fid, bmap(1), -1);
+            json{end+1}={keylist{i}, fread(fid,bmap(1),'uint8=>char')};
+        else
+            fmap=memmapfile(fname,'writable',false, 'offset',bmap(1),'format', rec);
+            json{end+1}={keylist{i}, char(fmap.Data(1).x)};
         end
     end
 end
+
+if(exist('fid','var') && fid>0)
+    fclose(fid);
+end
diff --git a/jsonset.m b/jsonset.m
index a80cac5..27a5fc9 100644
--- a/jsonset.m
+++ b/jsonset.m
@@ -3,7 +3,7 @@ function json=jsonset(fname,mmap,varargin)
 % json=jsonset(fname,mmap,'$.jsonpath1',newval1,'$.jsonpath2','newval2',...)
 %
 % Fast writing of JSON data records to stream or disk using memory-map 
-% (mmap) returned by loadjson and JSONPath-like keys
+% (mmap) returned by loadjson/loadbj and JSONPath-like keys
 %
 % authors:Qianqian Fang (q.fang <at> neu.edu)
 % initially created on 2022/02/02
@@ -22,11 +22,20 @@ function json=jsonset(fname,mmap,varargin)
 %            written
 %
 % examples:
-%      str='[[1,2],"a",{"c":2}]{"k":"test"}';
-%      [dat, mmap]=loadjson(str);
-%      savejson('',dat,'filename','mydata.json','compact',1);
-%      json=jsonset(str,mmap,'$.[2].c','5')
-%      json=jsonset('mydata.json',mmap,'$.[2].c','"c":5')
+%      % create test data
+%       d.arr={[1,2],'a',struct('c',2)}; d.obj=struct('k','test') 
+%      % convert to json string
+%       str=savejson('',d,'compact',1)
+%      % parse and return mmap
+%       [dat, mmap]=loadjson(str);
+%      % display mmap entries
+%       savejson('',mmap)
+%      % replace value using mmap
+%       json=jsonset(str,mmap,'$.arr.[2].c','5')
+%      % save same json string to file (must set savebinary 1)
+%       savejson('',d,'filename','file.json','compact',1,'savebinary',1);
+%      % fast write to file
+%       json=jsonset('file.json',mmap,'$.arr.[2].c','5')
 %
 % license:
 %     BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details 
@@ -37,7 +46,9 @@ function json=jsonset(fname,mmap,varargin)
 if(regexp(fname,'^\s*(?:\[.*\])|(?:\{.*\})\s*$','once'))
     inputstr=fname;
 else
-    fid=fopen(fname,'wb');
+    if(~exist('memmapfile','file'))
+        fid=fopen(fname,'r+b');
+    end
 end
 
 mmap=[mmap{:}];
@@ -55,22 +66,23 @@ for i=1:2:length(varargin)
     if(regexp(varargin{i},'^\$'))
         [tf,loc]=ismember(varargin{i},keylist);
         if(tf)
-            rec={'uint8',[1,mmap{loc*2}(2)],  'x'};
+            bmap=mmap{loc*2};
             if(ischar(varargin{i+1}))
                 val=varargin{i+1};
             else
                 val=savejson('',varargin{i+1},'compact',1);
             end
-            if(length(val)<=rec{1,2}(2))
-                val=[val repmat(' ',[1,rec{1,2}(2)-length(val)])];
+            if(length(val)<=bmap(2))
+                val=[val repmat(' ',[1,bmap(2)-length(val)])];
                 if(exist('inputstr','var'))
-                    inputstr(mmap{loc*2}(1):mmap{loc*2}(1)+mmap{loc*2}(2)-1)=val;
+                    inputstr(bmap(1):bmap(1)+bmap(2)-1)=val;
                 else
                     if(exist('memmapfile','file'))
-                        fmap=memmapfile(fname,'writable',true,'offset',mmap{loc*2}(1),'format', rec);
-                        fmap.x=val;
+                        rec={'uint8', [1 bmap(2)],  'x'};
+                        fmap=memmapfile(fname,'writable',true,'offset', bmap(1)-1, 'format', rec, 'repeat',1);
+                        fmap.Data.x=uint8(val);
                     else
-                        fseek(fid,mmap{loc*2}(1)-1,'bof');
+                        fseek(fid,bmap(1)-1,'bof');
                         fwrite(fid,val);
                     end
                     json{end+1}={varargin{i},val};
diff --git a/loadbj.m b/loadbj.m
index a42ff32..a94a515 100644
--- a/loadbj.m
+++ b/loadbj.m
@@ -58,6 +58,15 @@ function [data, mmap] = loadbj(fname,varargin)
 %                         for output format, it is incompatible with all
 %                         previous releases; if old output is desired,
 %                         please set FormatVersion to 1.9 or earlier.
+%           MmapOnly [0|1]: if set to 1, this function only returns mmap
+%           MMapInclude 'str1' or  {'str1','str2',..}: if provided, the
+%                         returned mmap will be filtered by only keeping
+%                         entries containing any one of the string patterns
+%                         provided in a cell
+%           MMapExclude 'str1' or  {'str1','str2',..}: if provided, the
+%                         returned mmap will be filtered by removing
+%                         entries containing any one of the string patterns
+%                         provided in a cell
 %
 % output:
 %      dat: a cell array, where {...} blocks are converted into cell arrays,
@@ -98,6 +107,7 @@ function [data, mmap] = loadbj(fname,varargin)
     opt.simplifycellarray=jsonopt('SimplifyCellArray',0,opt);
     opt.usemap=jsonopt('UseMap',0,opt);
     opt.nameisstring=jsonopt('NameIsString',0,opt);
+    mmaponly=jsonopt('MmapOnly',0,opt);
 
     [os,maxelem,systemendian]=computer;
     opt.flipendian_=(systemendian ~= upper(jsonopt('Endian','L',opt)));
@@ -108,22 +118,28 @@ function [data, mmap] = loadbj(fname,varargin)
         maxobjid=inf;
     end
 
-    mmap={};
     opt.jsonpath_='$';
+    if(nargout>1 || mmaponly)
+        mmap={};
+    end
     jsoncount=1;
     while pos <= inputlen
         [cc, pos]=next_char(inputstr, pos);
         switch(cc)
             case '{'
-                if(nargout>1)
+                if(nargout>1 || mmaponly)
+                    mmap{end+1}={opt.jsonpath_,pos};
                     [data{jsoncount}, pos, newmmap] = parse_object(inputstr, pos, opt);
+                    mmap{end}{2}=[mmap{end}{2},pos-mmap{end}{2}];
                     mmap=[mmap(:);newmmap(:)];
                 else
                     [data{jsoncount}, pos] = parse_object(inputstr, pos, opt);
                 end
             case '['
-                if(nargout>1)
+                if(nargout>1 || mmaponly)
+                    mmap{end+1}={opt.jsonpath_,pos};
                     [data{jsoncount}, pos, newmmap] = parse_array(inputstr, pos, opt);
+                    mmap{end}{2}=[mmap{end}{2},pos-mmap{end}{2}];
                     mmap=[mmap(:);newmmap(:)];
                 else
                     [data{jsoncount}, pos] = parse_array(inputstr, pos, opt);
@@ -133,9 +149,9 @@ function [data, mmap] = loadbj(fname,varargin)
             otherwise
                 error_pos('Outer level structure must be an object or an array', inputstr, pos);
         end
-	if(jsoncount>=maxobjid)
-	    break;
-	end
+        if(jsoncount>=maxobjid)
+            break;
+        end
         opt.jsonpath_=sprintf('$%d',jsoncount);
         jsoncount=jsoncount+1;
     end % while
@@ -148,7 +164,11 @@ function [data, mmap] = loadbj(fname,varargin)
     if(jsoncount==1 && iscell(data))
         data=data{1};
     end
-
+    if(nargout>1 || mmaponly)
+        mmap=mmap';
+        mmap=filterjsonmmap(mmap, jsonopt('MMapExclude',{},opt), 0);
+        mmap=filterjsonmmap(mmap, jsonopt('MMapInclude',{},opt), 1);
+    end
     if(jsonopt('JDataDecode',1,varargin{:})==1)
         try
             data=jdatadecode(data,'Base64',0,'Recursive',1,varargin{:});
@@ -158,6 +178,9 @@ function [data, mmap] = loadbj(fname,varargin)
                 ME.identifier, ME.message, savejson('',ME.stack));
         end
     end
+    if(mmaponly)
+        data=mmap;
+    end
 end
 
 %%-------------------------------------------------------------------------
@@ -179,7 +202,7 @@ end
 
 function [object, pos, mmap] = parse_array(inputstr,  pos, varargin) % JSON array is written in row-major order
     if(nargout>2)
-        mmap={{[varargin{1}.jsonpath_ '.[*]'],pos}};
+        mmap={};
         origpath=varargin{1}.jsonpath_;
     end
     pos=parse_char(inputstr, pos, '[');
@@ -216,9 +239,6 @@ function [object, pos, mmap] = parse_array(inputstr,  pos, varargin) % JSON arra
                 object=permute(reshape(object,fliplr(dim(:)')),length(dim):-1:1);
             end
             pos=pos+adv;
-            if(nargout>2)
-                mmap{1}{2}=[mmap{1}{2},pos-mmap{1}{2}];
-            end
             return;
         else
             endpos=match_bracket(inputstr,pos);
@@ -227,9 +247,6 @@ function [object, pos, mmap] = parse_array(inputstr,  pos, varargin) % JSON arra
             [object, adv]=parse_block(inputstr, pos, type,count,varargin{:});
             pos=pos+adv;
             pos=parse_char(inputstr, pos, ']');
-            if(nargout>2)
-                mmap{1}{2}=[mmap{1}{2},pos-mmap{1}{2}+1];
-            end
             return;
         end
     end
@@ -238,7 +255,9 @@ function [object, pos, mmap] = parse_array(inputstr,  pos, varargin) % JSON arra
          while 1
             if(nargout>2)
                 varargin{1}.jsonpath_=[origpath '.' sprintf('[%d]',length(object))];
+                mmap{end+1}={varargin{1}.jsonpath_, pos};
                 [val, pos, newmmap] = parse_value(inputstr, pos, varargin{:});
+                mmap{end}{2}=[mmap{end}{2}, pos-mmap{end}{2}];
                 mmap=[mmap(:);newmmap(:)];
             else
                 [val, pos] = parse_value(inputstr, pos, varargin{:});
@@ -277,9 +296,6 @@ function [object, pos, mmap] = parse_array(inputstr,  pos, varargin) % JSON arra
     if(count==-1)
         pos=parse_char(inputstr, pos, ']');
     end
-    if(nargout>2)
-        mmap{1}{2}=[mmap{1}{2},pos-mmap{1}{2}+1];
-    end
 end
 
 %%-------------------------------------------------------------------------
@@ -413,7 +429,8 @@ end
 %%-------------------------------------------------------------------------
 function [object, pos, mmap] = parse_object(inputstr, pos, varargin)
     if(nargout>2)
-        mmap={{varargin{1}.jsonpath_,pos}};
+        mmap={};
+        origpath=varargin{1}.jsonpath_;
     end
     pos=parse_char(inputstr,pos,'{');
     usemap=varargin{1}.usemap;
@@ -446,10 +463,8 @@ function [object, pos, mmap] = parse_object(inputstr, pos, varargin)
                 error_pos('Name of value at position %d cannot be empty', inputstr, pos);
             end
             if(nargout>2)
-                varargin{1}.jsonpath_=[mmap{1}{1},'.',str];
-                mmap{end+1}={varargin{1}.jsonpath_,pos-length(str)-2};
-            end
-            if(nargout>2)
+                varargin{1}.jsonpath_=[origpath,'.',str];
+                mmap{end+1}={varargin{1}.jsonpath_,pos};
                 [val, pos,newmmap] = parse_value(inputstr, pos, varargin{:});
                 mmap{end}{2}=[mmap{end}{2}, pos-mmap{end}{2}];
                 mmap=[mmap(:);newmmap(:)];
@@ -471,9 +486,6 @@ function [object, pos, mmap] = parse_object(inputstr, pos, varargin)
     if(count==-1)
         pos=parse_char(inputstr, pos, '}');
     end
-    if(nargout>2)
-        mmap{1}={[mmap{1}{1} '.*'],[mmap{1}{2}, pos-mmap{1}{2}]};
-    end
 end
 
 %%-------------------------------------------------------------------------
diff --git a/loadjson.m b/loadjson.m
index 08db99f..60d34d8 100644
--- a/loadjson.m
+++ b/loadjson.m
@@ -67,6 +67,15 @@ function [data, mmap] = loadjson(fname,varargin)
 %                         jsondecode, if presents (MATLAB R2016b or Octave
 %                         6) first. If jsondecode does not exist or failed, 
 %                         this function falls back to the jsonlab parser
+%           MmapOnly [0|1]: if set to 1, this function only returns mmap
+%           MMapInclude 'str1' or  {'str1','str2',..}: if provided, the
+%                         returned mmap will be filtered by only keeping
+%                         entries containing any one of the string patterns
+%                         provided in a cell
+%           MMapExclude 'str1' or  {'str1','str2',..}: if provided, the
+%                         returned mmap will be filtered by removing
+%                         entries containing any one of the string patterns
+%                         provided in a cell
 %
 % output:
 %      dat: a cell array, where {...} blocks are converted into cell arrays,
@@ -113,7 +122,6 @@ function [data, mmap] = loadjson(fname,varargin)
        error_pos('input file does not exist');
     end
 
-    mmap={};
     if(jsonopt('BuiltinJSON',0,opt) && exist('jsondecode','builtin'))
         try
             newstring=regexprep(string,'[\r\n]','');
@@ -143,6 +151,7 @@ function [data, mmap] = loadjson(fname,varargin)
     opt.parsestringarray=jsonopt('ParseStringArray',0,opt);
     opt.usemap=jsonopt('UseMap',0,opt);
     opt.arraydepth_=1;
+    mmaponly=jsonopt('MmapOnly',0,opt);
 
     if(jsonopt('ShowProgress',0,opt)==1)
         opt.progressbar_=waitbar(0,'loading ...');
@@ -154,20 +163,27 @@ function [data, mmap] = loadjson(fname,varargin)
         maxobjid=inf;
     end
     opt.jsonpath_='$';
+    if(nargout>1 || mmaponly)
+        mmap={};
+    end
     jsoncount=1;
     while pos <= inputlen
-        [cc,pos]=next_char(inputstr, pos);
+        [cc,pos,w1]=next_char(inputstr, pos);
         switch(cc)
             case '{'
-                if(nargout>1)
+                if(nargout>1 || mmaponly)
+                    mmap{end+1}={opt.jsonpath_,pos-w1};
                     [data{jsoncount},pos,index_esc,newmmap] = parse_object(inputstr, pos, esc, index_esc,opt);
+                    mmap{end}{2}=[mmap{end}{2},pos-mmap{end}{2}];
                     mmap=[mmap(:);newmmap(:)];
                 else
                     [data{jsoncount},pos,index_esc] = parse_object(inputstr, pos, esc, index_esc,opt);
                 end
             case '['
-                if(nargout>1)
+                if(nargout>1 || mmaponly)
+                    mmap{end+1}={opt.jsonpath_,pos-w1};
                     [data{jsoncount},pos,index_esc,newmmap] = parse_array(inputstr, pos, esc, index_esc,opt);
+                    mmap{end}{2}=[mmap{end}{2},pos-mmap{end}{2}];
                     mmap=[mmap(:);newmmap(:)];
                 else
                     [data{jsoncount},pos,index_esc] = parse_array(inputstr, pos, esc, index_esc,opt);
@@ -175,9 +191,9 @@ function [data, mmap] = loadjson(fname,varargin)
             otherwise
                 pos=error_pos('Outer level structure must be an object or an array',inputstr,pos);
         end
-	if(jsoncount>=maxobjid)
-	    break;
-	end
+	    if(jsoncount>=maxobjid)
+	        break;
+	    end
         opt.jsonpath_=sprintf('$%d',jsoncount);
         jsoncount=jsoncount+1;
     end % while
@@ -190,7 +206,11 @@ function [data, mmap] = loadjson(fname,varargin)
     if(jsoncount==1 && iscell(data))
         data=data{1};
     end
-
+    if(nargout>1 || mmaponly)
+        mmap=mmap';
+        mmap=filterjsonmmap(mmap, jsonopt('MMapExclude',{},opt), 0);
+        mmap=filterjsonmmap(mmap, jsonopt('MMapInclude',{},opt), 1);
+    end
     if(jsonopt('JDataDecode',1,varargin{:})==1)
         try
             data=jdatadecode(data,'Base64',1,'Recursive',1,varargin{:});
@@ -200,7 +220,9 @@ function [data, mmap] = loadjson(fname,varargin)
                 ME.identifier, ME.message, savejson('',ME.stack));
         end
     end
-    
+    if(mmaponly)
+        data=mmap;
+    end
     if(isfield(opt,'progressbar_'))
         close(opt.progressbar_);
     end
@@ -212,7 +234,7 @@ end
 
 function [object, pos,index_esc, mmap] = parse_array(inputstr, pos, esc, index_esc, varargin) % JSON array is written in row-major order
     if(nargout>3)
-        mmap={{[varargin{1}.jsonpath_ '.[*]'],pos}};
+        mmap={};
         origpath=varargin{1}.jsonpath_;
     end
     pos=parse_char(inputstr, pos, '[');
@@ -230,9 +252,6 @@ function [object, pos,index_esc, mmap] = parse_array(inputstr, pos, esc, index_e
         try
             if((varargin{1}.fastarrayparser)>=1 && arraydepth>=varargin{1}.fastarrayparser)
                 [endpos, maxlevel]=fast_match_bracket(varargin{1}.arraytoken_,varargin{1}.arraytokenidx_,pos);
-                if(nargout>3)
-                    mmap{1}{2}=[mmap{1}{2},endpos-mmap{1}{2}+1];
-                end
                 if(~isempty(endpos))
                     arraystr=['[' inputstr(pos:endpos)];
                     arraystr=sscanf_prep(arraystr);
@@ -292,7 +311,9 @@ function [object, pos,index_esc, mmap] = parse_array(inputstr, pos, esc, index_e
                 varargin{1}.arraydepth_=arraydepth+1;
                 if(nargout>3)
                     varargin{1}.jsonpath_=[origpath '.' sprintf('[%d]',length(object))];
-                    [val, pos,index_esc, newmmap] = parse_value(inputstr, pos, esc, index_esc,varargin{:});
+                    mmap{end+1}={varargin{1}.jsonpath_, pos};
+                    [val, pos, index_esc, newmmap] = parse_value(inputstr, pos, esc, index_esc,varargin{:});
+                    mmap{end}{2}=[mmap{end}{2}, pos-mmap{end}{2}];
                     mmap=[mmap(:);newmmap(:)];
                 else
                     [val, pos,index_esc] = parse_value(inputstr, pos, esc, index_esc,varargin{:});
@@ -339,19 +360,26 @@ function [object, pos,index_esc, mmap] = parse_array(inputstr, pos, esc, index_e
 end
 %%-------------------------------------------------------------------------
 
-function pos=parse_char(inputstr, pos, c)
+function [pos, w1, w2]=parse_char(inputstr, pos, c)
+    w1=pos;
+    w2=0;
     pos=skip_whitespace(pos, inputstr);
+    w1=pos-w1;
     if pos > length(inputstr) || inputstr(pos) ~= c
         pos=error_pos(sprintf('Expected %c at position %%d', c),inputstr,pos);
     else
         pos = pos + 1;
+        w2=pos;
         pos=skip_whitespace(pos, inputstr);
+        w2=pos-w2;
     end
 end
 %%-------------------------------------------------------------------------
 
-function [c, pos] = next_char(inputstr, pos)
+function [c, pos, w1] = next_char(inputstr, pos)
+    w1=pos;
     pos=skip_whitespace(pos, inputstr);
+    w1=pos-w1;
     if pos > length(inputstr)
         c = [];
     else
@@ -360,7 +388,10 @@ function [c, pos] = next_char(inputstr, pos)
 end
 
 %%-------------------------------------------------------------------------
-function [str, pos,index_esc] = parseStr(inputstr, pos, esc, index_esc, varargin)
+function [str, pos,index_esc, mmap] = parseStr(inputstr, pos, esc, index_esc, varargin)
+    if(nargout>3)
+        mmap={};
+    end
     if inputstr(pos) ~= '"'
         pos=error_pos('String starting with " expected at position %d',inputstr,pos);
     else
@@ -444,8 +475,7 @@ function varargout = parse_value(inputstr, pos, esc, index_esc, varargin)
     end
     switch(inputstr(pos))
         case '"'
-            [varargout{1:3}] = parseStr(inputstr, pos, esc, index_esc,varargin{:});
-            varargout{3}=index_esc;
+            [varargout{1:nargout}] = parseStr(inputstr, pos, esc, index_esc,varargin{:});
             return;
         case '['
             [varargout{1:nargout}] = parse_array(inputstr, pos, esc, index_esc, varargin{:});
@@ -481,7 +511,8 @@ end
 %%-------------------------------------------------------------------------
 function [object, pos, index_esc, mmap] = parse_object(inputstr, pos, esc, index_esc, varargin)
     if(nargout>3)
-        mmap={{varargin{1}.jsonpath_,pos}};
+        mmap={};
+        origpath=varargin{1}.jsonpath_;
     end
     pos=parse_char(inputstr, pos, '{');
     usemap=varargin{1}.usemap;
@@ -497,11 +528,11 @@ function [object, pos, index_esc, mmap] = parse_object(inputstr, pos, esc, index
             if isempty(str)
                 pos=error_pos('Name of value at position %d cannot be empty',inputstr,pos);
             end
+            pos=parse_char(inputstr, pos, ':');
             if(nargout>3)
-                varargin{1}.jsonpath_=[mmap{1}{1},'.',str];
-                mmap{end+1}={varargin{1}.jsonpath_,pos-length(str)-2};
+                varargin{1}.jsonpath_=[origpath,'.',str];
+                mmap{end+1}={varargin{1}.jsonpath_,pos};
             end
-            pos=parse_char(inputstr, pos, ':');
             if(nargout>3)
                 [val, pos,index_esc, newmmap] = parse_value(inputstr, pos, esc, index_esc, varargin{:});
                 mmap{end}{2}=[mmap{end}{2}, pos-mmap{end}{2}];
@@ -522,9 +553,6 @@ function [object, pos, index_esc, mmap] = parse_object(inputstr, pos, esc, index
         end
     end
     pos=parse_char(inputstr, pos, '}');
-    if(nargout>3)
-        mmap{1}={[mmap{1}{1} '.*'],[mmap{1}{2}, pos-mmap{1}{2}]};
-    end
 end
 
 %%-------------------------------------------------------------------------
diff --git a/savejson.m b/savejson.m
index 2bf4098..4ab2cc1 100644
--- a/savejson.m
+++ b/savejson.m
@@ -255,7 +255,7 @@ if(~isempty(filename))
         mode='a';
     end
     if(jsonopt('SaveBinary',0,opt)==1)
-        if(isempty(encoding))
+        if(~isempty(encoding))
             fid = fopen(filename, [mode 'b'],endian,encoding);
         else
             fid = fopen(filename, [mode 'b'],endian);
diff --git a/test/run_jsonlab_test.m b/test/run_jsonlab_test.m
index 0c68b95..5907691 100644
--- a/test/run_jsonlab_test.m
+++ b/test/run_jsonlab_test.m
@@ -24,7 +24,7 @@ function run_jsonlab_test(tests)
 %
 
 if(nargin==0)
-    tests={'js','jso','bj','bjo'};
+    tests={'js','jso','bj','bjo','jmap','bmap'};
 end
 
 %%
@@ -285,3 +285,49 @@ if(ismember('bjo',tests))
     test_jsonlab('test default float endian for savebj',@savebj,typecast(uint8('1e05'),'single'),'d1e05');
     test_jsonlab('test default float endian for saveubjson',@saveubjson,typecast(uint8('12345678'),'double'),'D87654321');
 end
+
+%%
+if(ismember('jmap',tests))
+    fprintf(sprintf('%s\n',char(ones(1,79)*61)));
+    fprintf('Test JSON mmap\n');
+    fprintf(sprintf('%s\n',char(ones(1,79)*61)));
+
+    test_jsonlab('mmap of a 1D numerical array',@savejson,loadjson('[1,2,3]','mmaponly',1),'[["$",[1,7]]]','compact',1);
+    test_jsonlab('mmap of a 1D mixed array',@savejson,loadjson('[1,"2",3]','mmaponly',1),'[["$",[1,9]]]','compact',1);
+    test_jsonlab('mmap of a 2D array',@savejson,loadjson('[[1,2,3],[4,5,6]]','mmaponly',1),'[["$",[1,17]]]','compact',1);
+    test_jsonlab('mmap of concatenated json',@savejson,loadjson('[1,2,3][4,5,6]','mmaponly',1),'[["$",[1,7]],["$1",[8,7]]]','compact',1);
+    test_jsonlab('mmap of concatenated json objects',@savejson,loadjson('[1,2,3]{"a":[4,5]}','mmaponly',1),'[["$",[1,7]],["$1",[8,11]],["$1.a",[13,5]]]','compact',1);
+    test_jsonlab('mmap of an array with an object',@savejson,loadjson('[1,2,{"a":3}]','mmaponly',1),...
+        '[["$",[1,13]],["$.[0]",[2,1]],["$.[1]",[4,1]],["$.[2]",[6,7]],["$.[2].a",[11,1]]]','compact',1);
+    test_jsonlab('mmap of an object',@savejson,loadjson('{"a":1,"b":[2,3]}','mmaponly',1),...
+        '[["$",[1,17]],["$.a",[6,1]],["$.b",[12,5]]]','compact',1);
+    test_jsonlab('mmap of object with white-space',@savejson,loadjson('{"a":1 , "b"  :  [2,3]}','mmaponly',1),...
+        '[["$",[1,23]],["$.a",[6,1]],["$.b",[18,5]]]','compact',1);
+    test_jsonlab('mmapinclude option',@savejson,loadjson('[[1,2,3],{"a":[4,5]}]','mmaponly',1,'mmapinclude','.a'),...
+        '[["$.[1].a",[15,5]]]','compact',1);
+    test_jsonlab('mmapexclude option',@savejson,loadjson('[[1,2,3],{"a":[4,5]}]','mmaponly',1,'mmapexclude',{'[0]','[1]','[2]'}),...
+        '[["$",[1,21]]]','compact',1);
+    test_jsonlab('json with indentation',@savejson,loadjson(savejson({[1,2,3],struct('a',[4,5])}),'mmaponly',1,'mmapinclude','.a'),...
+        '[["$.[1].a",[22,7]]]','compact',1);
+end
+
+%%
+if(ismember('bmap',tests))
+    fprintf(sprintf('%s\n',char(ones(1,79)*61)));
+    fprintf('Test Binary JSON mmap\n');
+    fprintf(sprintf('%s\n',char(ones(1,79)*61)));
+
+    test_jsonlab('mmap of a 1D numerical array',@savejson,loadbj(savebj([1,2,3]),'mmaponly',1),'[["$",[1,9]]]','compact',1);
+    test_jsonlab('mmap of a 1D mixed array',@savejson,loadbj(savebj({1,'2',3}),'mmaponly',1),'[["$",[1,8]],["$.[0]",[2,2]],["$.[1]",[4,2]],["$.[2]",[6,2]]]','compact',1);
+    test_jsonlab('mmap of a 2D array',@savejson,loadbj(savebj([[1,2,3],[4,5,6]]),'mmaponly',1),'[["$",[1,12]]]','compact',1);
+    test_jsonlab('mmap of an array with an object',@savejson,loadbj(savebj({1,2,struct('a',3)}),'mmaponly',1),...
+        '[["$",[1,13]],["$.[0]",[2,2]],["$.[1]",[4,2]],["$.[2]",[6,7]],["$.[2].a",[10,2]]]','compact',1);
+    test_jsonlab('mmap of an object',@savejson,loadbj(savebj(struct('a',1,'b',[2,3])),'mmaponly',1),...
+        '[["$",[1,18]],["$.a",[5,2]],["$.b",[10,8]]]','compact',1);
+    test_jsonlab('mmapinclude option',@savejson,loadbj(savebj({[1,2,3],struct('a',[4,5])}),'mmaponly',1,'mmapinclude','.a'),...
+        '[["$.[1].a",[15,8]]]','compact',1);
+    test_jsonlab('mmapexclude option',@savejson,loadbj(savebj({[1,2,3],struct('a',[4,5])}),'mmaponly',1,'mmapexclude',{'[0]','[1]','[2]'}),...
+        '[["$",[1,24]]]','compact',1);
+    test_jsonlab('json with indentation',@savejson,loadbj(savebj({[1,2,3],struct('a',[4,5])}),'mmaponly',1,'mmapinclude','.a'),...
+        '[["$.[1].a",[15,8]]]','compact',1);
+end
\ No newline at end of file
-- 
GitLab