|
| 1 | +classdef jdict < handle |
| 2 | + % |
| 3 | + % jd = jdict(data) |
| 4 | + % |
| 5 | + % A universal dictionary-like interface that enables fast multi-level subkey access and |
| 6 | + % JSONPath-based element indexing, such as jd.('key1').('key2') and jd.('$.key1.key2'), |
| 7 | + % for hierachical data structures embedding struct, containers.Map or dictionary objects |
| 8 | + % |
| 9 | + % author: Qianqian Fang (q.fang <at> neu.edu) |
| 10 | + % |
| 11 | + % input: |
| 12 | + % data: a hierachical data structure made of struct, containers.Map, dictionary, or cell arrays |
| 13 | + % if data is a string starting with http:// or https://, |
| 14 | + % loadjson(data) will be used to dynamically load the data |
| 15 | + % |
| 16 | + % indexing: |
| 17 | + % jd.('key1').('subkey1')... can retrieve values that are recursively index keys that are |
| 18 | + % jd.('key1').('subkey1').v(1) if the subkey key1 is an array, this can retrieve the first element |
| 19 | + % jd.('key1').('subkey1').v(1).('subsubkey1') the indexing can be further applied for deeper objects |
| 20 | + % jd.('$.key1.subkey1') if the indexing starts with '$' this allows a JSONPath based index |
| 21 | + % jd.('$.key1.subkey1[0]') using a JSONPath can also read array-based subkey element |
| 22 | + % jd.('$.key1.subkey1[0].subsubkey1') JSONPath can also apply further indexing over objects of diverse types |
| 23 | + % jd.('$.key1..subkey') JSONPath can use '..' deep-search operator to find and retrieve subkey appearing at any level below |
| 24 | + % |
| 25 | + % member functions: |
| 26 | + % jd() or jd.v() returns the underlying hierachical data |
| 27 | + % jd.('cell1').v(i) or jd.('array1').v(2:3) returns specified elements if the element is a cell or array |
| 28 | + % jd.('key1'),('subkey1').v() returns the underlying hierachical data at the specified subkeys |
| 29 | + % jd.tojson() convers the underlying data to a JSON string |
| 30 | + % jd.tojson('compression', 'zlib', ...) convers the data to a JSON string with savejson() options |
| 31 | + % 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 |
| 32 | + % jd.len() return the length of the sub-keys |
| 33 | + % |
| 34 | + % if using matlab, the .v(...) method can be replaced by bare |
| 35 | + % brackets .(...), but in octave, one must use .v(...) |
| 36 | + % |
| 37 | + % examples: |
| 38 | + % obj = struct('key1', struct('subkey1',1, 'subkey2',[1,2,3]), 'subkey2', 'str'); |
| 39 | + % obj.key1.subkey3 = {8,'test',containers.Map('subsubkey1',0)} |
| 40 | + % |
| 41 | + % jd = jdict(obj); |
| 42 | + % |
| 43 | + % % getting values |
| 44 | + % jd.('key1').('subkey1') % return jdict(1) |
| 45 | + % jd.('key1').('subkey3') % return jdict(obj.key1.subkey3) |
| 46 | + % jd.('key1').('subkey3')() % return obj.key1.subkey3 |
| 47 | + % jd.('key1').('subkey3').v(1) % return jdict({8}) |
| 48 | + % jd.('key1').('subkey3').('subsubkey1') % return jdict(obj.key1.subkey3(2)) |
| 49 | + % jd.('key1').('subkey3').v(2).v() % return {'test'} |
| 50 | + % jd.('$.key1.subkey1') % return jdict(1) |
| 51 | + % jd.('$.key1.subkey2')() % return 'str' |
| 52 | + % jd.('$.key1.subkey2').v().v(1) % return jdict(1) |
| 53 | + % jd.('$.key1.subkey2')().v(1).v() % return 1 |
| 54 | + % jd.('$.key1.subkey3[2].subsubkey1') % return jdict(0) |
| 55 | + % jd.('$..subkey2') % jsonpath '..' operator runs a deep scan, return jdict({'str', [1 2 3]}) |
| 56 | + % jd.('$..subkey2').v(2) % return jdict([1,2,3]) |
| 57 | + % |
| 58 | + % % setting values |
| 59 | + % jd.('subkey2') = 'newstr' % setting obj.subkey2 to 'newstr' |
| 60 | + % |
| 61 | + % license: |
| 62 | + % BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details |
| 63 | + % |
| 64 | + % -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) |
| 65 | + % |
| 66 | + |
| 67 | + properties (Access = private) |
| 68 | + data |
| 69 | + end |
| 70 | + methods |
| 71 | + |
| 72 | + function obj = jdict(val) |
| 73 | + if (nargin == 1) |
| 74 | + if (ischar(val) && ~isempty(regexp(val, '^https*://', 'once'))) |
| 75 | + try |
| 76 | + obj.data = loadjson(val); |
| 77 | + catch |
| 78 | + obj.data = val; |
| 79 | + end |
| 80 | + end |
| 81 | + if (isa(val, 'jdict')) |
| 82 | + obj = val; |
| 83 | + else |
| 84 | + obj.data = val; |
| 85 | + end |
| 86 | + end |
| 87 | + end |
| 88 | + |
| 89 | + % overloading the getter function |
| 90 | + function val = subsref(obj, idxkey) |
| 91 | + oplen = length(idxkey); |
| 92 | + val = obj.data; |
| 93 | + if (oplen == 1 && strcmp(idxkey.type, '()') && isempty(idxkey.subs)) |
| 94 | + return |
| 95 | + end |
| 96 | + i = 1; |
| 97 | + while i <= oplen |
| 98 | + idx = idxkey(i); |
| 99 | + % disp({i, savejson(idx)}); |
| 100 | + if (isempty(idx.subs)) |
| 101 | + i = i + 1; |
| 102 | + continue |
| 103 | + end |
| 104 | + if (idx.type == '.' && isnumeric(idx.subs)) |
| 105 | + val = val(idx.subs); |
| 106 | + elseif ((strcmp(idx.type, '()') || idx.type == '.') && ismember(idx.subs, {'tojson', 'fromjson', 'v', 'keys', 'len'}) && i < oplen) |
| 107 | + if (strcmp(idx.subs, 'v')) |
| 108 | + if (iscell(val) && strcmp(idxkey(i + 1).type, '()')) |
| 109 | + idxkey(i + 1).type = '{}'; |
| 110 | + end |
| 111 | + if (~isempty(idxkey(i + 1).subs)) |
| 112 | + val = v(jdict(val), idxkey(i + 1)); |
| 113 | + end |
| 114 | + else |
| 115 | + fhandle = str2func(idx.subs); |
| 116 | + val = fhandle(jdict(val), idxkey(i + 1).subs{:}); |
| 117 | + end |
| 118 | + i = i + 1; |
| 119 | + if (i < oplen) |
| 120 | + val = jdict(val); |
| 121 | + end |
| 122 | + elseif ((idx.type == '.' && ischar(idx.subs)) || (iscell(idx.subs) && ~isempty(idx.subs{1}))) |
| 123 | + onekey = idx.subs; |
| 124 | + if (iscell(onekey)) |
| 125 | + onekey = onekey{1}; |
| 126 | + end |
| 127 | + if (isa(val, 'jdict')) |
| 128 | + val = val.data; |
| 129 | + end |
| 130 | + if (ischar(onekey) && ~isempty(onekey) && onekey(1) == '$') |
| 131 | + val = jsonpath(val, onekey); |
| 132 | + elseif (isstruct(val)) |
| 133 | + val = val.(onekey); |
| 134 | + elseif (isa(val, 'containers.Map') || isa(val, 'dictionary')) |
| 135 | + val = val(onekey); |
| 136 | + else |
| 137 | + error('key name "%s" not found', onekey); |
| 138 | + end |
| 139 | + else |
| 140 | + error('method not supported'); |
| 141 | + end |
| 142 | + i = i + 1; |
| 143 | + end |
| 144 | + if (~(isempty(idxkey(end).subs) && strcmp(idxkey(end).type, '()'))) |
| 145 | + val = jdict(val); |
| 146 | + end |
| 147 | + end |
| 148 | + |
| 149 | + % overloading the setter function, obj.('idxkey')=otherobj |
| 150 | + function obj = subsasgn(obj, idxkey, otherobj) |
| 151 | + oplen = length(idxkey); |
| 152 | + val = obj.data; |
| 153 | + i = 1; |
| 154 | + while i <= oplen |
| 155 | + if (i > 1) |
| 156 | + error('multi-level assignment is not yet supported'); |
| 157 | + end |
| 158 | + idx = idxkey(i); |
| 159 | + if ((idx.type == '.' && ischar(idx.subs)) || (iscell(idx.subs) && ~isempty(idx.subs{1}))) |
| 160 | + onekey = idx.subs; |
| 161 | + if (iscell(onekey)) |
| 162 | + onekey = onekey{1}; |
| 163 | + end |
| 164 | + if (ischar(onekey) && ~isempty(onekey) && onekey(1) == '$') |
| 165 | + % jsonset(val, onekey) = otherobj; |
| 166 | + error('setting value via JSONPath is not supported'); |
| 167 | + elseif (isstruct(val)) |
| 168 | + obj.data.(onekey) = otherobj; |
| 169 | + elseif (isa(val, 'containers.Map') || isa(val, 'dictionary')) |
| 170 | + obj.data(onekey) = otherobj; |
| 171 | + end |
| 172 | + end |
| 173 | + i = i + 1; |
| 174 | + end |
| 175 | + end |
| 176 | + |
| 177 | + function val = tojson(obj, varargin) |
| 178 | + val = savejson('', obj.data, 'compact', 1, varargin{:}); |
| 179 | + end |
| 180 | + |
| 181 | + function obj = fromjson(obj, fname, varargin) |
| 182 | + obj.data = loadjd(fname, varargin{:}); |
| 183 | + end |
| 184 | + |
| 185 | + function val = keys(obj) |
| 186 | + if (isstruct(obj.data)) |
| 187 | + val = fieldnames(obj.data); |
| 188 | + elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary')) |
| 189 | + val = keys(obj.data); |
| 190 | + else |
| 191 | + val = 1:length(obj.data); |
| 192 | + end |
| 193 | + end |
| 194 | + |
| 195 | + function val = len(obj) |
| 196 | + val = length(obj.data); |
| 197 | + end |
| 198 | + |
| 199 | + function val = v(obj, varargin) |
| 200 | + if (~isempty(varargin)) |
| 201 | + val = subsref(obj.data, varargin{:}); |
| 202 | + else |
| 203 | + val = obj.data; |
| 204 | + end |
| 205 | + end |
| 206 | + |
| 207 | + end |
| 208 | +end |
0 commit comments