|
13 | 13 | % string starting with http:// or https://, loadjson(data) |
14 | 14 | % will be used to dynamically load the data |
15 | 15 | % |
| 16 | +% constructors: |
| 17 | +% jd = jdict creates an empty jdict object (like an empty struct or containers.Map) |
| 18 | +% jd = jdict(data) wraps any matlab data (array, cell, struct, dictionary, ...) into a new jdict object |
| 19 | +% jd = jdict(data, 'param1', value1, 'param2', value2, ...) use param/value pairs to initilize jd.flags |
| 20 | +% jd = jdict(data, 'attr', attrmap) initilize data attributes using a containers.Map with JSONPath as keys |
| 21 | +% |
| 22 | +% member functions: |
| 23 | +% jd.('cell1').v(i) or jd.('array1').v(2:3) returns specified elements if the element is a cell or array |
| 24 | +% jd.('key1').('subkey1').v() returns the underlying hierachical data at the specified subkeys |
| 25 | +% jd.tojson() convers the underlying data to a JSON string |
| 26 | +% jd.tojson('compression', 'zlib', ...) convers the data to a JSON string with savejson() options |
| 27 | +% jd.keys() returns the sub-key names of the object - if it a struct, dictionary or containers.Map - or 1:length(data) if it is an array |
| 28 | +% jd.len() returns the length of the sub-keys |
| 29 | +% jd.size() returns the dimension vector |
| 30 | +% jd.isKey(key) tests if a string-based key exists in the data, or number-based key is within the data array length |
| 31 | +% jd{'attrname'} gets/sets attributes using curly bracket indexing; jd{'attrname'}=val only works in MATLAB; use setattr() in octave |
| 32 | +% jd.setattr(jsonpath, attrname, value) sets attribute at any path |
| 33 | +% jd.getattr(jsonpath, attrname) gets attribute from any path |
| 34 | +% |
| 35 | +% if using matlab, the .v(...) method can be replaced by bare |
| 36 | +% brackets .(...), but in octave, one must use .v(...) |
| 37 | +% |
16 | 38 | % indexing: |
17 | 39 | % jd.('key1').('subkey1')... can retrieve values that are recursively index keys |
18 | 40 | % jd.key1.subkey1... can also retrieve the same data regardless |
|
24 | 46 | % jd.('$.key1.subkey1[0].subsubkey1') JSONPath can also apply further indexing over objects of diverse types |
25 | 47 | % jd.('$.key1..subkey') JSONPath can use '..' deep-search operator to find and retrieve subkey appearing at any level below |
26 | 48 | % |
27 | | -% member functions: |
28 | | -% jd() or jd.v() returns the underlying hierachical data |
29 | | -% jd.('cell1').v(i) or jd.('array1').v(2:3) returns specified elements if the element is a cell or array |
30 | | -% jd.('key1').('subkey1').v() returns the underlying hierachical data at the specified subkeys |
31 | | -% jd.tojson() convers the underlying data to a JSON string |
32 | | -% jd.tojson('compression', 'zlib', ...) convers the data to a JSON string with savejson() options |
33 | | -% 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 |
34 | | -% jd.len() return the length of the sub-keys |
35 | | -% jd.size() return the dimension vector |
36 | | -% jd{'attrname'} get/set attributes using curly brace indexing |
37 | | -% jd.setattr(jsonpath, attrname, value) set attribute at any path |
38 | | -% jd.getattr(jsonpath, attrname) get attribute from any path |
39 | | -% |
40 | | -% if using matlab, the .v(...) method can be replaced by bare |
41 | | -% brackets .(...), but in octave, one must use .v(...) |
42 | | -% |
43 | 49 | % examples: |
44 | 50 | % |
45 | 51 | % jd = jdict; |
|
101 | 107 |
|
102 | 108 | classdef jdict < handle |
103 | 109 | properties |
104 | | - data |
105 | | - attr |
| 110 | + data % underlying data: any matlab data (array, struct, cell, containers.Map, dictionary etc), retrieve via .v() |
| 111 | + attr % data attributes, stored via a containers.Map with JSONPath-based keys, retrieve via .getattr() or {} |
106 | 112 | end |
107 | 113 | properties (Access = private) |
108 | | - flags |
109 | | - currentpath |
| 114 | + flags % additional options, will be passed to jsonlab utility functions such as savejson/loadjson |
| 115 | + currentpath % internal variable tracking the current path when lookup embedded data at current depth |
110 | 116 | end |
111 | 117 | methods |
112 | 118 |
|
| 119 | + % constructor: initialize a jdict object |
113 | 120 | function obj = jdict(val, varargin) |
114 | 121 | obj.flags = struct('builtinjson', 0); |
115 | 122 | obj.attr = containers.Map(); |
|
118 | 125 | if (~isempty(varargin)) |
119 | 126 | allflags = [varargin(1:2:end); varargin(2:2:end)]; |
120 | 127 | obj.flags = struct(allflags{:}); |
| 128 | + if (isfield(obj.flags, 'attr')) |
| 129 | + obj.attr = obj.flags.attr; |
| 130 | + end |
121 | 131 | end |
122 | 132 | if (ischar(val) && ~isempty(regexpi(val, '^https*://', 'once'))) |
123 | 133 | try |
|
138 | 148 | end |
139 | 149 | end |
140 | 150 |
|
| 151 | + % overloaded indexing operator: handling assignments at arbitrary depths |
141 | 152 | function varargout = subsref(obj, idxkey) |
142 | 153 | % overloading the getter function jd.('key').('subkey') |
143 | 154 |
|
|
176 | 187 |
|
177 | 188 | if (strcmp(idx.type, '.') && isnumeric(idx.subs)) |
178 | 189 | val = val(idx.subs); |
179 | | - elseif ((strcmp(idx.type, '()') || strcmp(idx.type, '.')) && ischar(idx.subs) && ismember(idx.subs, {'tojson', 'fromjson', 'v', 'keys', 'len', 'size', 'setattr', 'getattr'}) && i < oplen) |
| 190 | + elseif ((strcmp(idx.type, '()') || strcmp(idx.type, '.')) && ischar(idx.subs) && ismember(idx.subs, {'tojson', 'fromjson', 'v', 'isKey', 'keys', 'len', 'size', 'setattr', 'getattr'}) && i < oplen) |
180 | 191 | if (strcmp(idx.subs, 'v')) |
181 | 192 | if (iscell(val) && strcmp(idxkey(i + 1).type, '()')) |
182 | 193 | idxkey(i + 1).type = '{}'; |
|
195 | 206 | tempobj.attr = obj.attr; |
196 | 207 | tempobj.currentpath = trackpath; |
197 | 208 | val = fhandle(tempobj, idxkey(i + 1).subs{:}); |
| 209 | + if (i == oplen - 1 && strcmp(idx.subs, 'isKey')) |
| 210 | + varargout{1} = val; |
| 211 | + return |
| 212 | + end |
198 | 213 | end |
199 | 214 | i = i + 1; |
200 | 215 | if (i < oplen) |
|
296 | 311 | varargout{1} = val; |
297 | 312 | end |
298 | 313 |
|
| 314 | + % overloaded assignment operator: handling assignments at arbitrary depths |
299 | 315 | function obj = subsasgn(obj, idxkey, otherobj) |
300 | 316 | % overloading the setter function, obj.('key').('subkey')=otherobj |
301 | 317 | % expanded from rahnema1's sample at https://stackoverflow.com/a/79030223/4271392 |
302 | 318 |
|
303 | | - % handle curly brace indexing for setting attributes |
| 319 | + % handle curly bracket indexing for setting attributes |
304 | 320 | oplen = length(idxkey); |
305 | 321 | if (oplen == 1 && strcmp(idxkey(1).type, '{}')) |
306 | 322 | if (iscell(idxkey(1).subs) && ~isempty(idxkey(1).subs)) |
|
475 | 491 | obj.data = opcell{1}; |
476 | 492 | end |
477 | 493 |
|
| 494 | + % export data to json |
478 | 495 | function val = tojson(obj, varargin) |
479 | 496 | % printing underlying data to compact-formed JSON string |
480 | 497 | val = obj.call_('savejson', '', obj.data, 'compact', 1, varargin{:}); |
481 | 498 | end |
482 | 499 |
|
| 500 | + % load data from over a dozen data formats, including json and binary json |
483 | 501 | function obj = fromjson(obj, fname, varargin) |
484 | 502 | % loading diverse data files using loadjd interface in jsonlab |
485 | 503 | obj.data = obj.call_('loadjd', fname, varargin{:}); |
|
496 | 514 | end |
497 | 515 | end |
498 | 516 |
|
| 517 | + % test if a key or index exists |
| 518 | + function val = isKey(obj, key) |
| 519 | + % list subfields at the current level |
| 520 | + if (isstruct(obj.data)) |
| 521 | + val = isfield(obj.data, key); |
| 522 | + elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary')) |
| 523 | + val = isKey(obj.data, key); |
| 524 | + else |
| 525 | + val = (key < length(obj.data)); |
| 526 | + end |
| 527 | + end |
| 528 | + |
| 529 | + % return the number of subfields or array length |
499 | 530 | function val = len(obj) |
500 | 531 | % return the number of subfields at the current level |
501 | 532 | if (isstruct(obj.data)) |
|
505 | 536 | end |
506 | 537 | end |
507 | 538 |
|
| 539 | + % return the dimension vector |
508 | 540 | function val = size(obj) |
509 | 541 | % return the dimension vector of the data |
510 | 542 | val = size(obj.data); |
511 | 543 | end |
512 | 544 |
|
| 545 | + % return the enclosed data |
513 | 546 | function val = v(obj, varargin) |
514 | 547 | if (~isempty(varargin)) |
515 | 548 | val = subsref(obj.data, varargin{:}); |
|
518 | 551 | end |
519 | 552 | end |
520 | 553 |
|
| 554 | + % internal: insert new key if does not exist |
521 | 555 | function val = newkey_(obj) |
522 | | - % insert new key if does not exist |
523 | 556 | if (exist('containers.Map')) |
524 | 557 | val = containers.Map; |
525 | 558 | else |
526 | 559 | val = struct; |
527 | 560 | end |
528 | 561 | end |
529 | 562 |
|
| 563 | + % internal: call member functions or external functions |
530 | 564 | function varargout = call_(obj, func, varargin) |
531 | 565 | % interface to external functions and dependencies |
532 | 566 | if (~obj.flags.builtinjson) |
|
550 | 584 | end |
551 | 585 | end |
552 | 586 |
|
| 587 | + % set specified data attributes |
553 | 588 | function attr = setattr(obj, jsonpath, attrname, attrvalue) |
554 | 589 | if (nargin == 3) |
555 | 590 | attrvalue = attrname; |
|
565 | 600 | attr = obj.attr; |
566 | 601 | end |
567 | 602 |
|
| 603 | + % return specified data attributes, if not specified, list all attributes |
568 | 604 | function val = getattr(obj, jsonpath, attrname) |
569 | 605 | if (nargin == 1) |
570 | 606 | if (isKey(obj.attr, obj.currentpath)) |
|
0 commit comments