Skip to content

Commit

Permalink
[test] add jsonpath test, refine jsonpath syntax support
Browse files Browse the repository at this point in the history
  • Loading branch information
fangq committed Mar 23, 2024
1 parent 22435e4 commit cff529a
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 26 deletions.
62 changes: 38 additions & 24 deletions jsonpath.m
Expand Up @@ -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)
Expand All @@ -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, '(?<start>-*\d*):(?<end>-*\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
Expand All @@ -91,7 +102,7 @@
if (~exist('newobj', 'var'))
newobj = {};
end
newobj = [newobj(:)', {val}];
newobj = [newobj(:)', val(:)'];
end
end
if (exist('newobj', 'var'))
Expand All @@ -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)};
Expand All @@ -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)
Expand All @@ -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
Expand Down
51 changes: 49 additions & 2 deletions test/run_jsonlab_test.m
Expand Up @@ -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
%
Expand All @@ -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:
Expand All @@ -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

%%
Expand Down Expand Up @@ -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)));
Expand Down

0 comments on commit cff529a

Please sign in to comment.