diff --git a/jsonpath.m b/jsonpath.m index 395a669..f5dfb6e 100644 --- a/jsonpath.m +++ b/jsonpath.m @@ -23,9 +23,13 @@ % obj = root; -jsonpath = regexprep(jsonpath, '([^.])(\[[0-9:\*]+\])', '$1.$2'); -[pat, paths] = regexp(jsonpath, '(\.{0,2}[^\s\.]+)', 'match', 'tokens'); +jsonpath = regexprep(jsonpath, '([^.\]])(\[[-0-9:\*]+\])', '$1.$2'); +jsonpath = regexprep(jsonpath, '\[[''"]*([^\]''"]+)[''"]*\]', '.[$1]'); +[pat, paths] = regexp(jsonpath, '(\.{0,2}[^\.]+)', 'match', 'tokens'); if (~isempty(pat) && ~isempty(paths)) + if (strcmp(paths{1}, '$')) + paths(1) = []; + end for i = 1:length(paths) [obj, isfound] = getonelevel(obj, paths, i); if (~isfound) @@ -52,34 +56,41 @@ obj = input; elseif (regexp(pathname, '$\d+')) obj = input(str2double(pathname(2:end)) + 1); -elseif (~isempty(regexp(pathname, '^\[[0-9\*:]+\]$', 'once')) || iscell(input)) +elseif (~isempty(regexp(pathname, '^\[[-0-9\*:]+\]$', 'once')) || iscell(input)) arraystr = pathname(2:end - 1); if (find(arraystr == ':')) - [arraystr, arrayrange] = regexp(arraystr, '(\d*):(\d*)', 'match', 'tokens'); - arrayrange = arrayrange{1}; - if (~isempty(arrayrange{1})) - arrayrange{1} = str2double(arrayrange{1}) + 1; + arrayrange = regexp(arraystr, '(?-*\d*):(?-*\d*)', 'names'); + if (~isempty(arrayrange.start)) + arrayrange.start = str2double(arrayrange.start); + arrayrange.start = (arrayrange.start < 0) * length(input) + arrayrange.start + 1; + else + arrayrange.start = 1; + end + if (~isempty(arrayrange.end)) + arrayrange.end = str2double(arrayrange.end); + arrayrange.end = (arrayrange.end < 0) * length(input) + arrayrange.end + 1; else - arrayrange{1} = 1; + arrayrange.end = length(input); end - if (~isempty(arrayrange{2})) - arrayrange{2} = str2double(arrayrange{2}) + 1; + elseif (regexp(arraystr, '^[-0-9:]+', 'once')) + firstidx = str2double(arraystr); + if (firstidx < 0) + firstidx = length(input) + firstidx + 1; else - arrayrange{2} = length(input); + firstidx = firstidx + 1; end - elseif (regexp(arraystr, '^[0-9:]+', 'once')) - arrayrange = str2double(arraystr) + 1; - arrayrange = {arrayrange, arrayrange}; + arrayrange.start = firstidx; + arrayrange.end = firstidx; elseif (~isempty(regexp(arraystr, '^\*', 'once'))) - arrayrange = {1, length(input)}; + % do nothing end if (exist('arrayrange', 'var')) - obj = {input{arrayrange{1}:arrayrange{2}}}; + obj = {input(arrayrange.start:arrayrange.end)}; else - arrayrange = {1, length(input)}; + arrayrange = struct('start', 1, 'end', length(input)); end if (~exist('obj', 'var') && iscell(input)) - input = {input{arrayrange{1}:arrayrange{2}}}; + input = {input{arrayrange.start:arrayrange.end}}; if (deepscan) searchkey = ['..' pathname]; else @@ -91,7 +102,7 @@ if (~exist('newobj', 'var')) newobj = {}; end - newobj = [newobj(:)', {val}]; + newobj = [newobj(:)', val(:)']; end end if (exist('newobj', 'var')) @@ -101,14 +112,19 @@ obj = obj{1}; end else - obj = input(arrayrange{1}:arrayrange{2}); + obj = input(arrayrange.start:arrayrange.end); end -elseif (isstruct(input) || isa(input, 'containers.Map')) +elseif (isstruct(input) || isa(input, 'containers.Map') || isa(input, 'table')) + pathname = regexprep(pathname, '^\[(.*)\]$', '$1'); stpath = encodevarname(pathname); if (isstruct(input)) if (isfield(input, stpath)) obj = {input.(stpath)}; end + elseif (isa(input, 'table')) + if (any(ismember(input.Properties.VariableNames, stpath))) + obj = {input.(stpath)}; + end else if (isKey(input, pathname)) obj = {input(pathname)}; @@ -122,7 +138,7 @@ if (~exist('obj', 'var')) obj = {}; end - obj = [obj(:)', {val}]; + obj = [obj(:)', val(:)']; end end if (exist('obj', 'var') && length(obj) == 1) @@ -132,8 +148,6 @@ if (exist('obj', 'var') && iscell(obj) && length(obj) == 1) obj = obj{1}; end -elseif (isa(input, 'table')) - obj = input(:, pathname); elseif (~deepscan) error('json path segment "%s" can not be found in the input object\n', pathname); end diff --git a/test/run_jsonlab_test.m b/test/run_jsonlab_test.m index 7bd6246..5e6a55f 100644 --- a/test/run_jsonlab_test.m +++ b/test/run_jsonlab_test.m @@ -3,7 +3,7 @@ function run_jsonlab_test(tests) % run_jsonlab_test % or % run_jsonlab_test(tests) -% run_jsonlab_test({'js','jso','bj','bjo','jmap','bmap','bugs'}) +% run_jsonlab_test({'js','jso','bj','bjo','jmap','bmap','jpath','bugs'}) % % Unit testing for JSONLab JSON, BJData/UBJSON encoders and decoders % @@ -18,6 +18,7 @@ function run_jsonlab_test(tests) % 'bjo': test savebj/loadbj special options % 'jmap': test jsonmmap features in loadjson % 'bmap': test jsonmmap features in loadbj +% 'jpath': test jsonpath % 'bugs': test specific bug fixes % % license: @@ -27,7 +28,7 @@ function run_jsonlab_test(tests) % if (nargin == 0) - tests = {'js', 'jso', 'bj', 'bjo', 'jmap', 'bmap', 'bugs'}; + tests = {'js', 'jso', 'bj', 'bjo', 'jmap', 'bmap', 'jpath', 'bugs'}; end %% @@ -353,6 +354,52 @@ function run_jsonlab_test(tests) '[["$1.b",[32,8]]]', 'compact', 1); end +%% +if (ismember('jpath', tests)) + fprintf(sprintf('%s\n', char(ones(1, 79) * 61))); + fprintf('Test JSONPath\n'); + fprintf(sprintf('%s\n', char(ones(1, 79) * 61))); + + testdata = struct('book', struct('title', {'Minch', 'Qui-Gon', 'Ben'}, 'author', {'Yoda', 'Jinn', 'Kenobi'}), 'game', struct('title', 'Mario')); + test_jsonlab('jsonpath of .key', @savejson, jsonpath(testdata, '$.game.title'), '"Mario"', 'compact', 1); + test_jsonlab('jsonpath of ..key', @savejson, jsonpath(testdata, '$.book..title'), '["Minch","Qui-Gon","Ben"]', 'compact', 1); + test_jsonlab('jsonpath of ..key cross objects', @savejson, jsonpath(testdata, '$..title'), '["Minch","Qui-Gon","Ben","Mario"]', 'compact', 1); + test_jsonlab('jsonpath of [index]', @savejson, jsonpath(testdata, '$..title[1]'), '["Qui-Gon"]', 'compact', 1); + test_jsonlab('jsonpath of [-index]', @savejson, jsonpath(testdata, '$..title[-1]'), '["Mario"]', 'compact', 1); + test_jsonlab('jsonpath of [start:end]', @savejson, jsonpath(testdata, '$..title[0:2]'), '["Minch","Qui-Gon","Ben"]', 'compact', 1); + test_jsonlab('jsonpath of [:end]', @savejson, jsonpath(testdata, '$..title[:2]'), '["Minch","Qui-Gon","Ben"]', 'compact', 1); + test_jsonlab('jsonpath of [start:]', @savejson, jsonpath(testdata, '$..title[1:]'), '["Qui-Gon","Ben","Mario"]', 'compact', 1); + test_jsonlab('jsonpath of [-start:-end]', @savejson, jsonpath(testdata, '$..title[-2:-1]'), '["Ben","Mario"]', 'compact', 1); + test_jsonlab('jsonpath of [-start:]', @savejson, jsonpath(testdata, '$..title[:-3]'), '["Minch","Qui-Gon"]', 'compact', 1); + test_jsonlab('jsonpath of [:-end]', @savejson, jsonpath(testdata, '$..title[-1:]'), '["Mario"]', 'compact', 1); + test_jsonlab('jsonpath of object with [index]', @savejson, jsonpath(testdata, '$.book[1]'), '{"title":"Qui-Gon","author":"Jinn"}', 'compact', 1); + test_jsonlab('jsonpath of element after [index]', @savejson, jsonpath(testdata, '$.book[1:2].author'), '["Jinn","Kenobi"]', 'compact', 1); + test_jsonlab('jsonpath of [*] and deep scan', @savejson, jsonpath(testdata, '$.book[*]..author'), '["Yoda","Jinn","Kenobi"]', 'compact', 1); + test_jsonlab('jsonpath of [*] after deep scan', @savejson, jsonpath(testdata, '$.book[*]..author[*]'), '["Yoda","Jinn","Kenobi"]', 'compact', 1); + test_jsonlab('jsonpath use [] instead of .', @savejson, jsonpath(testdata, '$[book][2][author]'), '"Kenobi"', 'compact', 1); + test_jsonlab('jsonpath use [] with [start:end]', @savejson, jsonpath(testdata, '$[book][1:2][author]'), '["Jinn","Kenobi"]', 'compact', 1); + test_jsonlab('jsonpath use . after [start:end]', @savejson, jsonpath(testdata, '$[book][0:1].author'), '["Yoda","Jinn"]', 'compact', 1); + test_jsonlab('jsonpath use [''*''] and ["*"]', @savejson, jsonpath(testdata, '$["book"][:-2][''author'']'), '["Yoda","Jinn"]', 'compact', 1); + test_jsonlab('jsonpath use combinations', @savejson, jsonpath(testdata, '$..["book"][:-2].author[*][0]'), '["Yoda"]', 'compact', 1); + + testdata = struct('book', struct(encodevarname('_title'), {'Minch', 'Qui-Gon', 'Ben'}, encodevarname(' author '), {'Yoda', 'Jinn', 'Kenobi'}), 'game', struct('title', 'Mario')); + test_jsonlab('jsonpath encoded field name in []', @savejson, jsonpath(testdata, '$..["book"][_title][*][0]'), '["Minch"]', 'compact', 1); + test_jsonlab('jsonpath encoded field name after .', @savejson, jsonpath(testdata, '$..["book"]._title[*][0]'), '["Minch"]', 'compact', 1); + test_jsonlab('jsonpath encoded field name after ..', @savejson, jsonpath(testdata, '$.._title'), '["Minch","Qui-Gon","Ben"]', 'compact', 1); + test_jsonlab('jsonpath encoded field name after .', @savejson, jsonpath(testdata, '$..["book"]['' author ''][*][1]'), '["Jinn"]', 'compact', 1); + + if (exist('containers.Map')) + testdata = struct('book', containers.Map({'title', 'author'}, {{'Minch', 'Qui-Gon', 'Ben'}, {'Yoda', 'Jinn', 'Kenobi'}}), 'game', struct('title', 'Mario')); + test_jsonlab('jsonpath use combinations', @savejson, jsonpath(testdata, '$..["book"].author[*][0]'), '["Yoda"]', 'compact', 1); + end + if (exist('istable')) + testdata = struct('book', table({'Minch', 'Qui-Gon', 'Ben'}, {'Yoda', 'Jinn', 'Kenobi'}, 'variablenames', {'title', 'author'}), 'game', struct('title', 'Mario')); + test_jsonlab('jsonpath use combinations', @savejson, jsonpath(testdata, '$..["book"].author[*][0]'), '["Yoda"]', 'compact', 1); + end + + clear testdata; +end + %% if (ismember('bugs', tests)) fprintf(sprintf('%s\n', char(ones(1, 79) * 61)));