|
31 | 31 | % jd.tojson('compression', 'zlib', ...) convers the data to a JSON string with savejson() options |
32 | 32 | % 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 |
33 | 33 | % 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 |
34 | 37 | % |
35 | 38 | % if using matlab, the .v(...) method can be replaced by bare |
36 | 39 | % brackets .(...), but in octave, one must use .v(...) |
|
65 | 68 | % jd.('key1').('subkey3').v(3).('subsubkey1') = 1; % modify keyed value |
66 | 69 | % jd.('key1').('subkey3').v(3).('subsubkey2') = 'new'; % add new key |
67 | 70 | % |
| 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 | +% |
68 | 79 | % % loading complex data from REST-API |
69 | 80 | % jd = jdict('https://neurojson.io:7777/cotilab/NeuroCaptain_2024'); |
70 | 81 | % |
|
86 | 97 | classdef jdict < handle |
87 | 98 | properties |
88 | 99 | data |
| 100 | + attr |
89 | 101 | end |
90 | 102 | properties (Access = private) |
91 | 103 | flags |
| 104 | + currentpath |
92 | 105 | end |
93 | 106 | methods |
94 | 107 |
|
95 | 108 | function obj = jdict(val, varargin) |
96 | 109 | obj.flags = struct('builtinjson', 0); |
| 110 | + obj.attr = containers.Map(); |
| 111 | + obj.currentpath = char(36); |
97 | 112 | if (nargin >= 1) |
98 | 113 | if (~isempty(varargin)) |
99 | 114 | allflags = [varargin(1:2:end); varargin(2:2:end)]; |
|
108 | 123 | return |
109 | 124 | end |
110 | 125 | 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; |
112 | 130 | else |
113 | 131 | obj.data = val; |
114 | 132 | end |
|
119 | 137 | % overloading the getter function jd.('key').('subkey') |
120 | 138 |
|
121 | 139 | 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 | + |
122 | 149 | 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)) |
124 | 153 | return |
125 | 154 | end |
126 | 155 | i = 1; |
|
130 | 159 | i = i + 1; |
131 | 160 | continue |
132 | 161 | 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)) |
134 | 171 | 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) |
136 | 173 | if (strcmp(idx.subs, 'v')) |
137 | 174 | if (iscell(val) && strcmp(idxkey(i + 1).type, '()')) |
138 | 175 | idxkey(i + 1).type = '{}'; |
139 | 176 | end |
140 | 177 | 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)); |
142 | 182 | end |
143 | 183 | else |
144 | 184 | 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{:}); |
146 | 189 | end |
147 | 190 | i = i + 1; |
148 | 191 | if (i < oplen) |
149 | | - val = jdict(val); |
| 192 | + tempobj = jdict(val); |
| 193 | + tempobj.attr = obj.attr; |
| 194 | + tempobj.currentpath = trackpath; |
| 195 | + val = tempobj; |
150 | 196 | end |
151 | 197 | elseif (strcmp(idx.type, '.') && ischar(idx.subs) && strcmp(idx.subs, 'v') && oplen == 1) |
152 | 198 | i = i + 1; |
153 | 199 | 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}))) |
155 | 201 | onekey = idx.subs; |
156 | 202 | if (iscell(onekey)) |
157 | 203 | onekey = onekey{1}; |
158 | 204 | end |
159 | 205 | if (isa(val, 'jdict')) |
160 | 206 | val = val.data; |
161 | 207 | end |
162 | | - if (ischar(onekey) && ~isempty(onekey) && onekey(1) == '$') |
| 208 | + if (ischar(onekey) && ~isempty(onekey) && onekey(1) == char(36)) |
163 | 209 | val = obj.call_('jsonpath', val, onekey); |
| 210 | + trackpath = onekey; |
164 | 211 | elseif (isstruct(val)) |
165 | 212 | val = val.(onekey); |
| 213 | + trackpath = [trackpath '.' onekey]; |
166 | 214 | elseif (isa(val, 'containers.Map') || isa(val, 'dictionary')) |
167 | 215 | val = val(onekey); |
| 216 | + trackpath = [trackpath '.' onekey]; |
168 | 217 | else |
169 | 218 | error('key name "%s" not found', onekey); |
170 | 219 | end |
|
173 | 222 | end |
174 | 223 | i = i + 1; |
175 | 224 | 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; |
178 | 233 | end |
179 | 234 | end |
180 | 235 |
|
181 | 236 | function obj = subsasgn(obj, idxkey, otherobj) |
182 | 237 | % overloading the setter function, obj.('key').('subkey')=otherobj |
183 | 238 | % 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 | + |
184 | 283 | oplen = length(idxkey); |
185 | 284 | opcell = cell (1, oplen + 1); |
186 | 285 | if (isempty(obj.data)) |
|
198 | 297 | end |
199 | 298 | continue |
200 | 299 | 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)) |
202 | 301 | error('setting values based on JSONPath indices is not yet supported'); |
203 | 302 | end |
204 | 303 | if (ischar(idx.subs)) |
|
316 | 415 | end |
317 | 416 | end |
318 | 417 |
|
| 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 | + |
319 | 450 | end |
320 | 451 | end |
0 commit comments