Skip to content

Commit 7d9410a

Browse files
committed
[jdict] jsonpath supports setting value, jdict can use jsonpath in setter, tests
1 parent 7804e8d commit 7d9410a

6 files changed

Lines changed: 109 additions & 34 deletions

File tree

jdatadecode.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,10 @@
345345
if (len == 1)
346346
newdata = newdata{1};
347347
end
348+
if (isfield(data, N_('_ArrayLabel_')))
349+
newdata = jdict(newdata);
350+
newdata{'dims'} = data(j).(N_('_ArrayLabel_'));
351+
end
348352
end
349353

350354
%% handle table data

jdataencode.m

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
% struct; otherwise, keep it as map
4848
% Compression: ['zlib'|'gzip','lzma','lz4','lz4hc'] - use zlib method
4949
% to compress data array
50-
% CompressArraySize: [100|int]: only to compress an array if the
50+
% CompressArraySize: [300|int]: only to compress an array if the
5151
% total element count is larger than this number.
5252
% FormatVersion [2|float]: set the JSONLab output version; since
5353
% v2.0, JSONLab uses JData specification Draft 1
@@ -86,7 +86,7 @@
8686
opt.compression = jsonopt('Compression', '', opt);
8787
opt.nestarray = jsonopt('NestArray', 0, opt);
8888
opt.formatversion = jsonopt('FormatVersion', 2, opt);
89-
opt.compressarraysize = jsonopt('CompressArraySize', 100, opt);
89+
opt.compressarraysize = jsonopt('CompressArraySize', 300, opt);
9090
opt.base64 = jsonopt('Base64', 0, opt);
9191
opt.mapasstruct = jsonopt('MapAsStruct', 0, opt);
9292
opt.usearrayzipsize = jsonopt('UseArrayZipSize', 1, opt);
@@ -102,7 +102,9 @@
102102
if (iscell(item))
103103
newitem = cell2jd(item, varargin{:});
104104
elseif (isa(item, 'jdict'))
105-
newitem = obj2jd(item(), varargin{:});
105+
attrpath = item.getattr();
106+
varargin{1}.annotatearray = ~isempty(attrpath);
107+
newitem = obj2jd(item.v(), varargin{:});
106108
elseif (isstruct(item))
107109
newitem = struct2jd(item, varargin{:});
108110
elseif (isnumeric(item) || islogical(item) || isa(item, 'timeseries'))
@@ -125,6 +127,26 @@
125127
newitem = item;
126128
end
127129

130+
if (isa(item, 'jdict')) % apply attribute
131+
if (isempty(attrpath))
132+
return
133+
end
134+
135+
newitem = jdict(newitem);
136+
137+
for i = 1:length(attrpath)
138+
attr = item.getattr(attrpath{i}).v();
139+
attrname = keys(attr);
140+
for j = 1:length(attrname)
141+
if (strcmp(attrname{j}, 'dims'))
142+
newitem.(attrpath{i}).(encodevarname('_ArrayLabel_')) = attr(attrname{j});
143+
else
144+
newitem.(attrpath{i}).(attrname{j}) = attr(attrname{j});
145+
end
146+
end
147+
end
148+
end
149+
128150
%% -------------------------------------------------------------------------
129151
function newitem = cell2jd(item, varargin)
130152

jdict.m

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@
206206
tempobj.attr = obj.attr;
207207
tempobj.currentpath = trackpath;
208208
val = fhandle(tempobj, idxkey(i + 1).subs{:});
209-
if (i == oplen - 1 && strcmp(idx.subs, 'isKey'))
209+
if (i == oplen - 1 && (strcmp(idx.subs, 'isKey') || strcmp(idx.subs, 'getattr')))
210210
varargout{1} = val;
211211
return
212212
end
@@ -431,10 +431,7 @@
431431
end
432432
continue
433433
end
434-
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
435-
error('setting values based on JSONPath indices is not yet supported');
436-
end
437-
if (ischar(idx.subs))
434+
if (ischar(idx.subs) && ~(~isempty(idx.subs) && idx.subs(1) == char(36)))
438435
if (((isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) && ~isKey(opcell{i}, idx.subs)))
439436
idx.type = '()';
440437
opcell{i}(idx.subs) = obj.newkey_();
@@ -448,7 +445,9 @@
448445
end
449446
end
450447
end
451-
if (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
448+
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
449+
opcell{i + 1} = obj.call_('jsonpath', opcell{i}, idx.subs);
450+
elseif (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
452451
opcell{i + 1} = opcell{i}(idx.subs);
453452
else
454453
opcell{i + 1} = subsref(opcell{i}, idx);
@@ -459,10 +458,14 @@
459458
opcell{i}(idx.subs) = otherobj;
460459
opcell{end - 1} = opcell{i};
461460
else
462-
if (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
463-
idx = struct('type', '()', 'subs', idx.subs);
461+
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
462+
opcell{end - 1} = obj.call_('jsonpath', opcell{i}, idx.subs, otherobj);
463+
else
464+
if (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
465+
idx = struct('type', '()', 'subs', idx.subs);
466+
end
467+
opcell{end - 1} = subsasgn(opcell{i}, idx, otherobj);
464468
end
465-
opcell{end - 1} = subsasgn(opcell{i}, idx, otherobj);
466469
end
467470

468471
for i = oplen - 1:-1:1
@@ -483,6 +486,8 @@
483486
opcell{i} = opcell{i + 1};
484487
end
485488
i = i - 1;
489+
elseif (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
490+
opcell{i} = obj.call_('jsonpath', opcell{i}, idx.subs, opcell{i + 1});
486491
else
487492
opcell{i} = subsasgn(opcell{i}, idx, opcell{i + 1});
488493
end
@@ -494,7 +499,7 @@
494499
% export data to json
495500
function val = tojson(obj, varargin)
496501
% printing underlying data to compact-formed JSON string
497-
val = obj.call_('savejson', '', obj.data, 'compact', 1, varargin{:});
502+
val = obj.call_('savejson', '', obj, 'compact', 1, varargin{:});
498503
end
499504

500505
% load data from over a dozen data formats, including json and binary json
@@ -611,15 +616,21 @@
611616
return
612617
end
613618
if (nargin == 2)
614-
attrname = jsonpath;
615-
jsonpath = obj.currentpath;
619+
if (~isempty(jsonpath) && jsonpath(1) ~= char(36))
620+
attrname = jsonpath;
621+
jsonpath = obj.currentpath;
622+
else
623+
attrname = '';
624+
end
616625
end
617626
if (~isKey(obj.attr, jsonpath))
618627
val = [];
619628
return
620629
end
621630
attrmap = obj.attr(jsonpath);
622-
if (isKey(attrmap, attrname))
631+
if (isempty(attrname))
632+
val = attrmap;
633+
elseif (isKey(attrmap, attrname))
623634
val = attrmap(attrname);
624635
else
625636
val = [];

jsonpath.m

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
function obj = jsonpath(root, jpath, varargin)
22
%
33
% obj=jsonpath(root, jpath)
4+
% obj=jsonpath(root, jpath, newvalue)
45
%
5-
% Query and retrieve elements from matlab data structures using JSONPath
6+
% Getting or setting data elements from matlab data structures using
7+
% JSONPath
68
%
79
% author: Qianqian Fang (q.fang <at> neu.edu)
810
%
911
% input:
1012
% root: a matlab data structure like an array, cell, struct, etc
1113
% jpath: a string in the format of JSONPath, see loadjson help
14+
% newvalue: if present, the data specified at the path jpath will be
15+
% replaced by newvalue
1216
%
1317
% output:
1418
% obj: if the specified element exist, obj returns the result
@@ -34,11 +38,29 @@
3438
if (strcmp(paths{1}, '$'))
3539
paths(1) = [];
3640
end
37-
for i = 1:length(paths)
38-
[obj, isfound] = getonelevel(obj, paths, i, varargin{:});
39-
if (~isfound)
40-
return
41+
if (isempty(varargin))
42+
for i = 1:length(paths)
43+
[obj, isfound] = getonelevel(obj, paths, i, varargin{:});
44+
if (~isfound)
45+
return
46+
end
47+
end
48+
else
49+
datastack = cell(1, length(paths) + 1);
50+
datastack{1} = obj;
51+
for i = 1:length(paths)
52+
[datastack{i + 1}, isfound] = getonelevel(datastack{i}, paths, i, varargin{:});
53+
if (~isfound)
54+
error(['data with a jsonpath ' jpath ' does not exist']);
55+
end
56+
end
57+
idx = struct('type', '.', 'subs', paths{end}{1}(2:end));
58+
datastack{end - 1} = subsasgn(jdict(datastack{i}), idx, varargin{1});
59+
for i = length(paths) - 1:-1:1
60+
idx = struct('type', '.', 'subs', paths{i}{1}(2:end));
61+
datastack{i} = subsasgn(jdict(datastack{i}), idx, datastack{i + 1}.v());
4162
end
63+
obj = datastack{1}.v();
4264
end
4365
end
4466

@@ -118,7 +140,7 @@
118140
else
119141
obj = input(arrayrange.start:arrayrange.end);
120142
end
121-
elseif (isstruct(input) || isa(input, 'containers.Map') || isa(input, 'table'))
143+
elseif (isstruct(input) || isa(input, 'containers.Map') || isa(input, 'table') || isa(input, 'jdict'))
122144
pathname = regexprep(pathname, '^\[(.*)\]$', '$1');
123145
stpath = encodevarname(pathname);
124146
if (isstruct(input))

savejson.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
% array dimensions, and
8989
% "_ArrayZipData_": the "base64" encoded
9090
% compressed binary array data.
91-
% CompressArraySize [100|int]: only to compress an array if the total
91+
% CompressArraySize [300|int]: only to compress an array if the total
9292
% element count is larger than this number.
9393
% CompressStringSize [inf|int]: only to compress a string if the total
9494
% element count is larger than this number.
@@ -155,13 +155,13 @@
155155

156156
opt.isoctave = isoctavemesh;
157157

158-
opt.compression = jsonopt('Compression', '', opt);
158+
opt.compression = jsonopt('Compression', 'zlib', opt);
159159
opt.nestarray = jsonopt('NestArray', 0, opt);
160160
opt.compact = jsonopt('Compact', 0, opt);
161161
opt.singletcell = jsonopt('SingletCell', 1, opt);
162162
opt.singletarray = jsonopt('SingletArray', 0, opt);
163163
opt.formatversion = jsonopt('FormatVersion', 3, opt);
164-
opt.compressarraysize = jsonopt('CompressArraySize', 100, opt);
164+
opt.compressarraysize = jsonopt('CompressArraySize', 300, opt);
165165
opt.compressstringsize = jsonopt('CompressStringSize', inf, opt);
166166
opt.intformat = jsonopt('IntFormat', '%.0f', opt);
167167
opt.floatformat = jsonopt('FloatFormat', '%.16g', opt);
@@ -304,7 +304,7 @@
304304
if (iscell(item) || (isa(item, 'string') && numel(item) > 1))
305305
txt = cell2json(name, item, level, varargin{:});
306306
elseif (isa(item, 'jdict'))
307-
txt = obj2json(name, item, level, varargin{:});
307+
txt = obj2json(name, item.v(), level, varargin{:});
308308
elseif (isstruct(item))
309309
txt = struct2json(name, item, level, varargin{:});
310310
elseif (isnumeric(item) || islogical(item) || isa(item, 'timeseries'))

test/run_jsonlab_test.m

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function run_jsonlab_test(tests)
33
% run_jsonlab_test
44
% or
55
% run_jsonlab_test(tests)
6-
% run_jsonlab_test({'js','jso','bj','bjo','jmap','bmap','jpath','jdict','bugs'})
6+
% run_jsonlab_test({'js','jso','bj','bjo','jmap','bmap','jpath','jdict','bugs','yaml','yamlopt','xarray'})
77
%
88
% Unit testing for JSONLab JSON, BJData/UBJSON encoders and decoders
99
%
@@ -21,6 +21,9 @@ function run_jsonlab_test(tests)
2121
% 'jpath': test jsonpath
2222
% 'jdict': test jdict
2323
% 'bugs': test specific bug fixes
24+
% 'yaml': test yaml reader/writer
25+
% 'yamlopt': test yaml handling options
26+
% 'xarray': test jdict data attribute operations
2427
%
2528
% license:
2629
% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details
@@ -461,6 +464,19 @@ function run_jsonlab_test(tests)
461464
jd.('key1').('subkey3').v(3).('subsubkey2') = 'new';
462465
test_jsonlab('jd.(''key1'').(''subkey3'')', @savejson, jd.('key1').('subkey3'), '[8,"mod",{"subsubkey1":1,"subsubkey2":"new"}]', 'compact', 1);
463466
test_jsonlab('jd.(''key1'').(''subkey2'')', @savejson, jd.('key1').('subkey2'), '[2,10,11]', 'compact', 1);
467+
test_jsonlab('jd.(''key1'').(''subkey2'').len()', @savejson, jd.('key1').('subkey2').len(), '[3]', 'compact', 1);
468+
test_jsonlab('jd.(''key1'').(''subkey2'').size()', @savejson, jd.('key1').('subkey2').size(), '[1,3]', 'compact', 1);
469+
test_jsonlab('jd.(''key1'').keys()', @savejson, jd.('key1').keys(), '[["subkey1"],["subkey2"],["subkey3"]]', 'compact', 1);
470+
test_jsonlab('jd.(''key1'').isKey(''subkey3'')', @savejson, jd.('key1').isKey('subkey3'), '[true]', 'compact', 1);
471+
test_jsonlab('jd.(''key1'').isKey(''subkey4'')', @savejson, jd.('key1').isKey('subkey4'), '[false]', 'compact', 1);
472+
473+
jd.('$.key1.subkey1') = [1, 2, 3];
474+
test_jsonlab('jd.(''$.key1.subkey1'')', @savejson, jd.('key1').('subkey1'), '[1,2,3]', 'compact', 1);
475+
jd.key1.('$.subkey1') = 'newsubkey1';
476+
test_jsonlab('jd.key1.(''$.subkey1'')', @savejson, jd.('key1').('subkey1'), '"newsubkey1"', 'compact', 1);
477+
jd.('$.key1').subkey1 = struct('newkey', 1);
478+
test_jsonlab('jd.(''$.key1'').subkey1', @savejson, jd.('key1').('subkey1'), '{"newkey":1}', 'compact', 1);
479+
test_jsonlab('jd.(''$.key1.subkey1'').newkey', @savejson, jd.('key1').('subkey1').newkey, '[1]', 'compact', 1);
464480

465481
clear testdata jd;
466482
end
@@ -702,13 +718,13 @@ function run_jsonlab_test(tests)
702718
jd7.('data'){'dims'} = {'x', 'y'};
703719
jd7.('data'){'sampling_rate'} = 1000;
704720
end
705-
r11a = jd7.('data'){'dims'};
706-
r11b = jd7.('data'){'sampling_rate'};
707-
if (iscell(r11a) && r11b == 1000)
708-
fprintf(1, 'Testing attr persistence: ok\n');
709-
else
710-
warning('Test attr persistence: failed');
711-
end
721+
test_jsonlab('dims attribute in level 1', @savejson, jd7.('data'){'dims'}, '["x","y"]', 'compact', 1);
722+
test_jsonlab('other attribute in level 1', @savejson, jd7.('data'){'sampling_rate'}, '[1000]', 'compact', 1);
723+
test_jsonlab('getattr list top attr key', @savejson, jd7.getattr(), '["$.data"]', 'compact', 1);
724+
test_jsonlab('getattr return all attributes', @savejson, jd7.getattr('$.data').v(), '{"dims":["x","y"],"sampling_rate":1000}', 'compact', 1);
725+
test_jsonlab('getattr get one attr', @savejson, jd7.getattr('$.data', 'dims').v(), '["x","y"]', 'compact', 1);
726+
test_jsonlab('savejson with _ArrayLabel_', @savejson, jd7, '{"data":{"_ArrayType_":"double","_ArraySize_":[3,4],"_ArrayData_":[1,1,1,1,1,1,1,1,1,1,1,1],"_ArrayLabel_":["x","y"],"sampling_rate":1000}}', 'compact', 1);
727+
test_jsonlab('loadjson with _ArrayLabel_', @savejson, loadjson(jd7.tojson()).data.getattr('$', 'dims'), '["x","y"]', 'compact', 1);
712728

713729
% Test 12: Multiple attributes different types
714730
jd8 = jdict(rand(10, 20));

0 commit comments

Comments
 (0)