Skip to content

Commit d40b6ed

Browse files
committed
[xarray] major new feature: support getting and setting attributes, like xarray
1 parent 31d25b0 commit d40b6ed

2 files changed

Lines changed: 323 additions & 13 deletions

File tree

jdict.m

Lines changed: 143 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
% jd.tojson('compression', 'zlib', ...) convers the data to a JSON string with savejson() options
3232
% jd.keys() return the sub-key names of the object - if it a struct, dictionary or containers.Map - or 1:length(data) if it is an array
3333
% jd.len() return the length of the sub-keys
34+
% jd{'attrname'} get/set attributes using curly brace indexing
35+
% jd.setattr(jsonpath, attrname, value) set attribute at any path
36+
% jd.getattr(jsonpath, attrname) get attribute from any path
3437
%
3538
% if using matlab, the .v(...) method can be replaced by bare
3639
% brackets .(...), but in octave, one must use .v(...)
@@ -65,6 +68,14 @@
6568
% jd.('key1').('subkey3').v(3).('subsubkey1') = 1; % modify keyed value
6669
% jd.('key1').('subkey3').v(3).('subsubkey2') = 'new'; % add new key
6770
%
71+
% % attributes
72+
% jd{'dims'} = {'x','y','z'}; % set attribute
73+
% %% {'attr'} is MATLAB-only
74+
% jd.('a'){'dims'} = {'time','space'}; % set attribute on nested element(MATLAB only, Octave use .setattr())
75+
% %% use .setattr() in MATLAB/Octave
76+
% jd.('a').setattr('dims',{'time','space'}); % set attributes
77+
% jd.('a'){'dims'} % print attribute on nested element
78+
%
6879
% % loading complex data from REST-API
6980
% jd = jdict('https://neurojson.io:7777/cotilab/NeuroCaptain_2024');
7081
%
@@ -86,14 +97,18 @@
8697
classdef jdict < handle
8798
properties
8899
data
100+
attr
89101
end
90102
properties (Access = private)
91103
flags
104+
currentpath
92105
end
93106
methods
94107

95108
function obj = jdict(val, varargin)
96109
obj.flags = struct('builtinjson', 0);
110+
obj.attr = containers.Map();
111+
obj.currentpath = char(36);
97112
if (nargin >= 1)
98113
if (~isempty(varargin))
99114
allflags = [varargin(1:2:end); varargin(2:2:end)];
@@ -108,7 +123,10 @@
108123
return
109124
end
110125
if (isa(val, 'jdict'))
111-
obj = val;
126+
obj.data = val.data;
127+
obj.attr = val.attr;
128+
obj.currentpath = val.currentpath;
129+
obj.flags = val.flags;
112130
else
113131
obj.data = val;
114132
end
@@ -119,8 +137,19 @@
119137
% overloading the getter function jd.('key').('subkey')
120138

121139
oplen = length(idxkey);
140+
141+
% handle curly brace indexing for attributes
142+
if (oplen == 1 && strcmp(idxkey(1).type, '{}'))
143+
if (iscell(idxkey(1).subs) && length(idxkey(1).subs) == 1 && ischar(idxkey(1).subs{1}))
144+
val = obj.getattr(obj.currentpath, idxkey(1).subs{1});
145+
return
146+
end
147+
end
148+
122149
val = obj.data;
123-
if (oplen == 1 && strcmp(idxkey.type, '()') && isempty(idxkey.subs))
150+
trackpath = obj.currentpath;
151+
152+
if (oplen == 1 && strcmp(idxkey(1).type, '()') && isempty(idxkey(1).subs))
124153
return
125154
end
126155
i = 1;
@@ -130,41 +159,61 @@
130159
i = i + 1;
131160
continue
132161
end
133-
if (idx.type == '.' && isnumeric(idx.subs))
162+
163+
% handle {} attribute access in navigation chain
164+
if (strcmp(idx.type, '{}') && iscell(idx.subs) && length(idx.subs) == 1 && ischar(idx.subs{1}))
165+
val = obj.getattr(trackpath, idx.subs{1});
166+
i = i + 1;
167+
continue
168+
end
169+
170+
if (strcmp(idx.type, '.') && isnumeric(idx.subs))
134171
val = val(idx.subs);
135-
elseif ((strcmp(idx.type, '()') || strcmp(idx.type, '.')) && ischar(idx.subs) && ismember(idx.subs, {'tojson', 'fromjson', 'v', 'keys', 'len'}) && i < oplen)
172+
elseif ((strcmp(idx.type, '()') || strcmp(idx.type, '.')) && ischar(idx.subs) && ismember(idx.subs, {'tojson', 'fromjson', 'v', 'keys', 'len', 'setattr', 'getattr'}) && i < oplen)
136173
if (strcmp(idx.subs, 'v'))
137174
if (iscell(val) && strcmp(idxkey(i + 1).type, '()'))
138175
idxkey(i + 1).type = '{}';
139176
end
140177
if (~isempty(idxkey(i + 1).subs))
141-
val = v(jdict(val), idxkey(i + 1));
178+
tempobj = jdict(val);
179+
tempobj.attr = obj.attr;
180+
tempobj.currentpath = trackpath;
181+
val = v(tempobj, idxkey(i + 1));
142182
end
143183
else
144184
fhandle = str2func(idx.subs);
145-
val = fhandle(jdict(val), idxkey(i + 1).subs{:});
185+
tempobj = jdict(val);
186+
tempobj.attr = obj.attr;
187+
tempobj.currentpath = trackpath;
188+
val = fhandle(tempobj, idxkey(i + 1).subs{:});
146189
end
147190
i = i + 1;
148191
if (i < oplen)
149-
val = jdict(val);
192+
tempobj = jdict(val);
193+
tempobj.attr = obj.attr;
194+
tempobj.currentpath = trackpath;
195+
val = tempobj;
150196
end
151197
elseif (strcmp(idx.type, '.') && ischar(idx.subs) && strcmp(idx.subs, 'v') && oplen == 1)
152198
i = i + 1;
153199
continue
154-
elseif ((idx.type == '.' && ischar(idx.subs)) || (iscell(idx.subs) && ~isempty(idx.subs{1})))
200+
elseif ((strcmp(idx.type, '.') && ischar(idx.subs)) || (iscell(idx.subs) && ~isempty(idx.subs{1})))
155201
onekey = idx.subs;
156202
if (iscell(onekey))
157203
onekey = onekey{1};
158204
end
159205
if (isa(val, 'jdict'))
160206
val = val.data;
161207
end
162-
if (ischar(onekey) && ~isempty(onekey) && onekey(1) == '$')
208+
if (ischar(onekey) && ~isempty(onekey) && onekey(1) == char(36))
163209
val = obj.call_('jsonpath', val, onekey);
210+
trackpath = onekey;
164211
elseif (isstruct(val))
165212
val = val.(onekey);
213+
trackpath = [trackpath '.' onekey];
166214
elseif (isa(val, 'containers.Map') || isa(val, 'dictionary'))
167215
val = val(onekey);
216+
trackpath = [trackpath '.' onekey];
168217
else
169218
error('key name "%s" not found', onekey);
170219
end
@@ -173,14 +222,64 @@
173222
end
174223
i = i + 1;
175224
end
176-
if (~(isempty(idxkey(end).subs) && (strcmp(idxkey(end).type, '()') || strcmp(idxkey(end).type, '{}'))))
177-
val = jdict(val);
225+
226+
if ((strcmp(idxkey(end).type, '{}') && iscell(idxkey(end).subs) && length(idxkey(end).subs) == 1 && ischar(idxkey(end).subs{1})))
227+
return
228+
elseif (~(isempty(idxkey(end).subs) && (strcmp(idxkey(end).type, '()') || strcmp(idxkey(end).type, '{}'))))
229+
newobj = jdict(val);
230+
newobj.attr = obj.attr;
231+
newobj.currentpath = trackpath;
232+
val = newobj;
178233
end
179234
end
180235

181236
function obj = subsasgn(obj, idxkey, otherobj)
182237
% overloading the setter function, obj.('key').('subkey')=otherobj
183238
% expanded from rahnema1's sample at https://stackoverflow.com/a/79030223/4271392
239+
240+
% handle curly brace indexing for setting attributes
241+
oplen = length(idxkey);
242+
if (oplen == 1 && strcmp(idxkey(1).type, '{}'))
243+
if (iscell(idxkey(1).subs) && ~isempty(idxkey(1).subs))
244+
attrn = idxkey(1).subs{1};
245+
if (ischar(attrn))
246+
obj.setattr(obj.currentpath, attrn, otherobj);
247+
return
248+
end
249+
end
250+
end
251+
252+
% handle compound indexing like jd.('a'){'dims'} = value
253+
if (oplen >= 2 && strcmp(idxkey(oplen).type, '{}'))
254+
if (iscell(idxkey(oplen).subs) && ~isempty(idxkey(oplen).subs))
255+
attrn = idxkey(oplen).subs{1};
256+
if (ischar(attrn))
257+
% Build the path by navigating through keys
258+
temppath = obj.currentpath;
259+
for i = 1:oplen - 1
260+
idx = idxkey(i);
261+
if (strcmp(idx.type, '.') || strcmp(idx.type, '()'))
262+
if (iscell(idx.subs))
263+
onekey = idx.subs{1};
264+
else
265+
onekey = idx.subs;
266+
end
267+
if (ischar(onekey) && ~isempty(onekey))
268+
if (onekey(1) ~= char(36))
269+
temppath = [temppath '.' onekey];
270+
else
271+
temppath = onekey;
272+
end
273+
end
274+
end
275+
end
276+
% set attribute on original object with computed path
277+
obj.setattr(temppath, attrn, otherobj);
278+
return
279+
end
280+
end
281+
end
282+
184283
oplen = length(idxkey);
185284
opcell = cell (1, oplen + 1);
186285
if (isempty(obj.data))
@@ -198,7 +297,7 @@
198297
end
199298
continue
200299
end
201-
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == '$')
300+
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
202301
error('setting values based on JSONPath indices is not yet supported');
203302
end
204303
if (ischar(idx.subs))
@@ -316,5 +415,37 @@
316415
end
317416
end
318417

418+
function attr = setattr(obj, jsonpath, attrname, attrvalue)
419+
if (nargin == 3)
420+
attrvalue = attrname;
421+
attrname = jsonpath;
422+
jsonpath = obj.currentpath;
423+
end
424+
if (~isKey(obj.attr, jsonpath))
425+
obj.attr(jsonpath) = containers.Map();
426+
end
427+
attrmap = obj.attr(jsonpath);
428+
attrmap(attrname) = attrvalue;
429+
obj.attr(jsonpath) = attrmap;
430+
attr = obj.attr;
431+
end
432+
433+
function val = getattr(obj, jsonpath, attrname)
434+
if (nargin == 2)
435+
attrname = jsonpath;
436+
jsonpath = obj.currentpath;
437+
end
438+
if (~isKey(obj.attr, jsonpath))
439+
val = [];
440+
return
441+
end
442+
attrmap = obj.attr(jsonpath);
443+
if (isKey(attrmap, attrname))
444+
val = attrmap(attrname);
445+
else
446+
val = [];
447+
end
448+
end
449+
319450
end
320451
end

0 commit comments

Comments
 (0)