Skip to content

Commit ba581f5

Browse files
committed
[opt] cleaning up kind validation, add help for data kind, isfield,rmfield
1 parent b19169d commit ba581f5

File tree

1 file changed

+89
-43
lines changed

1 file changed

+89
-43
lines changed

jdict.m

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
% jd.len() returns the length of the sub-keys
3030
% jd.size() returns the dimension vector
3131
% jd.isKey(key) tests if a string-based key exists in the data, or number-based key is within the data array length
32+
% jd.isfield(key) same as isKey()
33+
% jd.rmfield(key) remove key from a struct/containers.Map/dictionary
3234
% jd{'attrname'} gets/sets attributes using curly bracket indexing; jd{'attrname'}=val only works in MATLAB; use setattr() in octave
3335
% jd.setattr(jsonpath, attrname, value) sets attribute at any path
3436
% jd.getattr(jsonpath, attrname) gets attribute from any path
@@ -81,6 +83,23 @@
8183
% schema = jd.attr2schema('title', 'Nested Example') exports the
8284
% schema-attributes as a JSON schema object
8385
%
86+
% Built-in schema-guarded data "kind" (fixed-format struct)
87+
% jd = jdict([], 'kind', 'date') forces the data to follow the date
88+
% built-in schema: which requires year/month/day with
89+
% positive integer values within a range;
90+
% jd.year = 2026
91+
% jd.day = 20
92+
% jd.month = 12 : above are allowed, assigning values to a built-in
93+
% kind automatically performs schema-based validation
94+
% jd.month = 13 : triggers an error Schema validation failed for "$.month": $: value > maximum;
95+
% jd() shows the formatted date in string '2026-12-20'
96+
% jd.v() shows a struct with year/day/month fields as raw data
97+
%
98+
% jd = jdict([], 'kind', 'uuid') creates an UUID object with default values
99+
% jd.keys() lists the UUID subfields
100+
% jd() prints the UUID
101+
%
102+
%
84103
% examples:
85104
%
86105
% jd = jdict;
@@ -130,6 +149,15 @@
130149
% jd.setschema(schema)
131150
% err = jd.validate()
132151
%
152+
% % schema-guarded data-kind ('uuid', 'date', 'time', 'datetime', 'bytes')
153+
% jd = jdict([], 'kind', 'date') % create a date using builtin-schema
154+
% jd.keys() % show date fields ('day','month','year')
155+
% jd.year = 2026 % set the year, auto-verified by schema
156+
% jd.month = 1 % set the month
157+
% jd.day = 20 % set the day
158+
% jd() % show the current date
159+
% %jd.month = 13 % this raises a schema-validation error
160+
%
133161
% % JSON Schema validation
134162
% jd = jdict(struct('name','John','age',30));
135163
% schema = struct('type','object',...
@@ -187,7 +215,7 @@
187215
obj.attr = obj.flags__.attr;
188216
end
189217
if (isfield(obj.flags__, 'schema'))
190-
obj.schema = obj.flags__.schema;
218+
obj.setschema(obj.flags__.schema);
191219
end
192220
if (isfield(obj.flags__, 'kind'))
193221
kindval = obj.flags__.kind;
@@ -204,7 +232,7 @@
204232
if (isa(val, 'jdict'))
205233
obj.data = val.data;
206234
obj.attr = val.attr;
207-
obj.schema = val.schema;
235+
obj.setschema(val.schema);
208236
obj.currentpath__ = val.currentpath__;
209237
obj.flags__ = val.flags__;
210238
else
@@ -220,6 +248,9 @@
220248
error('Unknown kind "%s" and no schema defined. Use: uuid, date, time, datetime, ipv4, ipv6, email, uri, posint, nonnegative', kindval);
221249
end
222250
obj.setattr(char(36), 'kind', kindval);
251+
if (isempty(obj.data))
252+
obj.data = obj.call_('jsonschema', kindschema, [], 'generate', 'all');
253+
end
223254
end
224255
end
225256

@@ -288,7 +319,7 @@
288319
if (~isempty(idxkey(i + 1).subs))
289320
tempobj = jdict(val);
290321
tempobj.attr = obj.attr;
291-
tempobj.schema = obj.schema;
322+
tempobj.setschema(obj.schema);
292323
tempobj.currentpath__ = trackpath;
293324
val = v(tempobj, idxkey(i + 1));
294325
elseif (isa(val, 'jdict'))
@@ -297,7 +328,7 @@
297328
else
298329
tempobj = jdict(val);
299330
tempobj.attr = obj.attr;
300-
tempobj.schema = obj.schema;
331+
tempobj.setschema(obj.schema);
301332
tempobj.currentpath__ = trackpath;
302333
if (obj.flags__.isoctave_ && regexp(OCTAVE_VERSION, '^5\.'))
303334
val = membercall_(tempobj, idx.subs, idxkey(i + 1).subs{:});
@@ -307,7 +338,7 @@
307338
end
308339
if (i == oplen - 1 && ismember(idx.subs, {'isKey', 'tojson', 'getattr', 'getschema', 'setschema', 'validate', 'attr2schema'}))
309340
if (strcmp(idx.subs, 'setschema'))
310-
obj.schema = tempobj.schema;
341+
obj.setschema(tempobj.schema);
311342
end
312343
varargout{1} = val;
313344
return
@@ -317,7 +348,7 @@
317348
if (i < oplen)
318349
tempobj = jdict(val);
319350
tempobj.attr = obj.attr;
320-
tempobj.schema = obj.schema;
351+
tempobj.setschema(obj.schema);
321352
tempobj.currentpath__ = trackpath;
322353
val = tempobj;
323354
end
@@ -342,7 +373,7 @@
342373
val = subsref(val, subsargs);
343374
newobj = jdict(val);
344375
newobj.attr = obj.attr;
345-
newobj.schema = obj.schema;
376+
newobj.setschema(obj.schema);
346377
newobj.currentpath__ = trackpath;
347378
newobj.root__ = obj.root__;
348379
val = newobj;
@@ -420,7 +451,7 @@
420451
newobj.attr(strrep(attrkeys{i}, trackpath, char(36))) = obj.attr(attrkeys{i});
421452
end
422453
end
423-
newobj.schema = obj.schema;
454+
newobj.setschema(obj.schema);
424455
newobj.currentpath__ = trackpath;
425456
newobj.root__ = obj.root__;
426457
val = newobj;
@@ -527,17 +558,27 @@
527558
end
528559
end
529560

561+
% validate if kind is set
562+
kindval = '';
563+
if (~isempty(obj.attr) && isKey(obj.attr, '$') && ~isempty(obj.attr('$')) && isKey(obj.attr('$'), 'kind'))
564+
kindval = obj.attr('$');
565+
kindval = kindval('kind');
566+
end
567+
% check if kind-validation is needed
568+
needvalidate = (~isempty(obj.schema) && ~isempty(kindval));
569+
if (needvalidate)
570+
tempobj = jdict();
571+
tempobj.setschema(obj.schema);
572+
datapath = buildpath_(obj.currentpath__, idxkey, oplen);
573+
end
574+
530575
% Fast path: single-level assignment like jd.key = value
531576
if (oplen == 1 && strcmp(idxkey(1).type, '.') && ischar(idxkey(1).subs))
532577
fieldname = idxkey(1).subs;
533578
% Skip if JSONPath
534579
if (isempty(fieldname) || fieldname(1) ~= char(36))
535-
% validate if kind is set
536-
kindval = obj.getattr(char(36), 'kind');
537-
if ~isempty(obj.schema) && ~isempty(kindval)
580+
if needvalidate
538581
targetpath = [obj.currentpath__ '.' esckey_(fieldname)];
539-
tempobj = jdict();
540-
tempobj.schema = obj.schema;
541582
tempobj.currentpath__ = targetpath;
542583
le(tempobj, otherobj);
543584
end
@@ -569,11 +610,8 @@
569610
% Fast path: single numeric index like jd.(1) = value
570611
if (oplen == 1 && strcmp(idxkey(1).type, '.') && isnumeric(idxkey(1).subs))
571612
% validate if kind is set
572-
kindval = obj.getattr(char(36), 'kind');
573-
if ~isempty(obj.schema) && ~isempty(kindval)
613+
if needvalidate
574614
targetpath = [obj.currentpath__ '[' num2str(idxkey(1).subs - 1) ']'];
575-
tempobj = jdict();
576-
tempobj.schema = obj.schema;
577615
tempobj.currentpath__ = targetpath;
578616
le(tempobj, otherobj);
579617
end
@@ -699,16 +737,10 @@
699737
end
700738
end
701739

702-
% check if kind-validation is needed
703-
kindval = obj.getattr(char(36), 'kind');
704-
needvalidate = ~isempty(obj.schema) && ~isempty(kindval);
705-
706740
if (oplen >= 2 && ischar(idxkey(oplen - 1).subs) && strcmp(idxkey(oplen - 1).subs, 'v') && strcmp(idxkey(oplen).type, '()'))
707741
% Handle .v(index) = value at any depth
708742
if needvalidate
709-
tempobj = jdict();
710-
tempobj.schema = obj.schema;
711-
tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen);
743+
tempobj.currentpath__ = datapath;
712744
le(tempobj, otherobj);
713745
end
714746
nextsubs = idxkey(oplen).subs;
@@ -726,18 +758,14 @@
726758
opcell{oplen + 1} = opcell{oplen};
727759
elseif (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{oplen}))
728760
if needvalidate
729-
tempobj = jdict();
730-
tempobj.schema = obj.schema;
731-
tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen);
761+
tempobj.currentpath__ = datapath;
732762
le(tempobj, otherobj);
733763
end
734764
opcell{oplen}(idx.subs) = otherobj;
735765
opcell{oplen + 1} = opcell{oplen};
736766
else
737767
if needvalidate
738-
tempobj = jdict();
739-
tempobj.schema = obj.schema;
740-
tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen);
768+
tempobj.currentpath__ = datapath;
741769
le(tempobj, otherobj);
742770
end
743771
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
@@ -839,6 +867,10 @@
839867
val = keys(obj);
840868
end
841869

870+
function val = isfield(obj, key)
871+
val = isKey(obj, key);
872+
end
873+
842874
% test if a key or index exists
843875
function val = isKey(obj, key)
844876
% list subfields at the current level
@@ -851,6 +883,20 @@
851883
end
852884
end
853885

886+
% remove specified key or element
887+
function val = rmfield(obj, key)
888+
% list subfields at the current level
889+
if (isstruct(obj.data))
890+
val = rmfield(obj.data, key);
891+
elseif (ismap_(obj.flags__, obj.data))
892+
val = remove(obj.data, key);
893+
elseif (iscell(obj.data))
894+
obj.data = builtin('subsasgn', obj.data, struct('type', '{}', 'subs', {{key}}), []);
895+
else
896+
obj.data = builtin('subsasgn', obj.data, struct('type', '()', 'subs', {{key}}), []);
897+
end
898+
end
899+
854900
% return the number of subfields or array length
855901
function val = len(obj)
856902
% return the number of subfields at the current level
@@ -937,23 +983,23 @@
937983
end
938984

939985
% set specified data attributes
940-
function attr = setattr(obj, jsonpath, attrname, attrvalue)
986+
function attr = setattr(obj, datapath, attrname, attrvalue)
941987
if (nargin == 3)
942988
attrvalue = attrname;
943-
attrname = jsonpath;
944-
jsonpath = obj.currentpath__;
989+
attrname = datapath;
990+
datapath = obj.currentpath__;
945991
end
946-
if (~isKey(obj.attr, jsonpath))
947-
obj.attr(jsonpath) = containers.Map();
992+
if (~isKey(obj.attr, datapath))
993+
obj.attr(datapath) = containers.Map();
948994
end
949-
attrmap = obj.attr(jsonpath);
995+
attrmap = obj.attr(datapath);
950996
attrmap(attrname) = attrvalue;
951-
obj.attr(jsonpath) = attrmap;
997+
obj.attr(datapath) = attrmap;
952998
attr = obj.attr;
953999
end
9541000

9551001
% return specified data attributes, if not specified, list all attributes
956-
function val = getattr(obj, jsonpath, attrname)
1002+
function val = getattr(obj, datapath, attrname)
9571003
val = [];
9581004
if (nargin == 1)
9591005
if (~isKey(obj.attr, obj.currentpath__) && strcmp(obj.currentpath__, char(36)))
@@ -964,17 +1010,17 @@
9641010
return
9651011
end
9661012
if (nargin == 2)
967-
if (~isempty(jsonpath) && jsonpath(1) ~= char(36))
968-
attrname = jsonpath;
969-
jsonpath = obj.currentpath__;
1013+
if (~isempty(datapath) && datapath(1) ~= char(36))
1014+
attrname = datapath;
1015+
datapath = obj.currentpath__;
9701016
else
9711017
attrname = '';
9721018
end
9731019
end
974-
if (~isKey(obj.attr, jsonpath))
1020+
if (~isKey(obj.attr, datapath))
9751021
return
9761022
end
977-
attrmap = obj.attr(jsonpath);
1023+
attrmap = obj.attr(datapath);
9781024
if (isempty(attrname))
9791025
val = attrmap;
9801026
elseif (isKey(attrmap, attrname))

0 commit comments

Comments
 (0)