|
29 | 29 | % jd.len() returns the length of the sub-keys |
30 | 30 | % jd.size() returns the dimension vector |
31 | 31 | % 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 |
32 | 34 | % jd{'attrname'} gets/sets attributes using curly bracket indexing; jd{'attrname'}=val only works in MATLAB; use setattr() in octave |
33 | 35 | % jd.setattr(jsonpath, attrname, value) sets attribute at any path |
34 | 36 | % jd.getattr(jsonpath, attrname) gets attribute from any path |
|
81 | 83 | % schema = jd.attr2schema('title', 'Nested Example') exports the |
82 | 84 | % schema-attributes as a JSON schema object |
83 | 85 | % |
| 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 | +% |
84 | 103 | % examples: |
85 | 104 | % |
86 | 105 | % jd = jdict; |
|
130 | 149 | % jd.setschema(schema) |
131 | 150 | % err = jd.validate() |
132 | 151 | % |
| 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 | +% |
133 | 161 | % % JSON Schema validation |
134 | 162 | % jd = jdict(struct('name','John','age',30)); |
135 | 163 | % schema = struct('type','object',... |
|
187 | 215 | obj.attr = obj.flags__.attr; |
188 | 216 | end |
189 | 217 | if (isfield(obj.flags__, 'schema')) |
190 | | - obj.schema = obj.flags__.schema; |
| 218 | + obj.setschema(obj.flags__.schema); |
191 | 219 | end |
192 | 220 | if (isfield(obj.flags__, 'kind')) |
193 | 221 | kindval = obj.flags__.kind; |
|
204 | 232 | if (isa(val, 'jdict')) |
205 | 233 | obj.data = val.data; |
206 | 234 | obj.attr = val.attr; |
207 | | - obj.schema = val.schema; |
| 235 | + obj.setschema(val.schema); |
208 | 236 | obj.currentpath__ = val.currentpath__; |
209 | 237 | obj.flags__ = val.flags__; |
210 | 238 | else |
|
220 | 248 | error('Unknown kind "%s" and no schema defined. Use: uuid, date, time, datetime, ipv4, ipv6, email, uri, posint, nonnegative', kindval); |
221 | 249 | end |
222 | 250 | obj.setattr(char(36), 'kind', kindval); |
| 251 | + if (isempty(obj.data)) |
| 252 | + obj.data = obj.call_('jsonschema', kindschema, [], 'generate', 'all'); |
| 253 | + end |
223 | 254 | end |
224 | 255 | end |
225 | 256 |
|
|
288 | 319 | if (~isempty(idxkey(i + 1).subs)) |
289 | 320 | tempobj = jdict(val); |
290 | 321 | tempobj.attr = obj.attr; |
291 | | - tempobj.schema = obj.schema; |
| 322 | + tempobj.setschema(obj.schema); |
292 | 323 | tempobj.currentpath__ = trackpath; |
293 | 324 | val = v(tempobj, idxkey(i + 1)); |
294 | 325 | elseif (isa(val, 'jdict')) |
|
297 | 328 | else |
298 | 329 | tempobj = jdict(val); |
299 | 330 | tempobj.attr = obj.attr; |
300 | | - tempobj.schema = obj.schema; |
| 331 | + tempobj.setschema(obj.schema); |
301 | 332 | tempobj.currentpath__ = trackpath; |
302 | 333 | if (obj.flags__.isoctave_ && regexp(OCTAVE_VERSION, '^5\.')) |
303 | 334 | val = membercall_(tempobj, idx.subs, idxkey(i + 1).subs{:}); |
|
307 | 338 | end |
308 | 339 | if (i == oplen - 1 && ismember(idx.subs, {'isKey', 'tojson', 'getattr', 'getschema', 'setschema', 'validate', 'attr2schema'})) |
309 | 340 | if (strcmp(idx.subs, 'setschema')) |
310 | | - obj.schema = tempobj.schema; |
| 341 | + obj.setschema(tempobj.schema); |
311 | 342 | end |
312 | 343 | varargout{1} = val; |
313 | 344 | return |
|
317 | 348 | if (i < oplen) |
318 | 349 | tempobj = jdict(val); |
319 | 350 | tempobj.attr = obj.attr; |
320 | | - tempobj.schema = obj.schema; |
| 351 | + tempobj.setschema(obj.schema); |
321 | 352 | tempobj.currentpath__ = trackpath; |
322 | 353 | val = tempobj; |
323 | 354 | end |
|
342 | 373 | val = subsref(val, subsargs); |
343 | 374 | newobj = jdict(val); |
344 | 375 | newobj.attr = obj.attr; |
345 | | - newobj.schema = obj.schema; |
| 376 | + newobj.setschema(obj.schema); |
346 | 377 | newobj.currentpath__ = trackpath; |
347 | 378 | newobj.root__ = obj.root__; |
348 | 379 | val = newobj; |
|
420 | 451 | newobj.attr(strrep(attrkeys{i}, trackpath, char(36))) = obj.attr(attrkeys{i}); |
421 | 452 | end |
422 | 453 | end |
423 | | - newobj.schema = obj.schema; |
| 454 | + newobj.setschema(obj.schema); |
424 | 455 | newobj.currentpath__ = trackpath; |
425 | 456 | newobj.root__ = obj.root__; |
426 | 457 | val = newobj; |
|
527 | 558 | end |
528 | 559 | end |
529 | 560 |
|
| 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 | + |
530 | 575 | % Fast path: single-level assignment like jd.key = value |
531 | 576 | if (oplen == 1 && strcmp(idxkey(1).type, '.') && ischar(idxkey(1).subs)) |
532 | 577 | fieldname = idxkey(1).subs; |
533 | 578 | % Skip if JSONPath |
534 | 579 | 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 |
538 | 581 | targetpath = [obj.currentpath__ '.' esckey_(fieldname)]; |
539 | | - tempobj = jdict(); |
540 | | - tempobj.schema = obj.schema; |
541 | 582 | tempobj.currentpath__ = targetpath; |
542 | 583 | le(tempobj, otherobj); |
543 | 584 | end |
|
569 | 610 | % Fast path: single numeric index like jd.(1) = value |
570 | 611 | if (oplen == 1 && strcmp(idxkey(1).type, '.') && isnumeric(idxkey(1).subs)) |
571 | 612 | % validate if kind is set |
572 | | - kindval = obj.getattr(char(36), 'kind'); |
573 | | - if ~isempty(obj.schema) && ~isempty(kindval) |
| 613 | + if needvalidate |
574 | 614 | targetpath = [obj.currentpath__ '[' num2str(idxkey(1).subs - 1) ']']; |
575 | | - tempobj = jdict(); |
576 | | - tempobj.schema = obj.schema; |
577 | 615 | tempobj.currentpath__ = targetpath; |
578 | 616 | le(tempobj, otherobj); |
579 | 617 | end |
|
699 | 737 | end |
700 | 738 | end |
701 | 739 |
|
702 | | - % check if kind-validation is needed |
703 | | - kindval = obj.getattr(char(36), 'kind'); |
704 | | - needvalidate = ~isempty(obj.schema) && ~isempty(kindval); |
705 | | - |
706 | 740 | if (oplen >= 2 && ischar(idxkey(oplen - 1).subs) && strcmp(idxkey(oplen - 1).subs, 'v') && strcmp(idxkey(oplen).type, '()')) |
707 | 741 | % Handle .v(index) = value at any depth |
708 | 742 | if needvalidate |
709 | | - tempobj = jdict(); |
710 | | - tempobj.schema = obj.schema; |
711 | | - tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen); |
| 743 | + tempobj.currentpath__ = datapath; |
712 | 744 | le(tempobj, otherobj); |
713 | 745 | end |
714 | 746 | nextsubs = idxkey(oplen).subs; |
|
726 | 758 | opcell{oplen + 1} = opcell{oplen}; |
727 | 759 | elseif (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{oplen})) |
728 | 760 | if needvalidate |
729 | | - tempobj = jdict(); |
730 | | - tempobj.schema = obj.schema; |
731 | | - tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen); |
| 761 | + tempobj.currentpath__ = datapath; |
732 | 762 | le(tempobj, otherobj); |
733 | 763 | end |
734 | 764 | opcell{oplen}(idx.subs) = otherobj; |
735 | 765 | opcell{oplen + 1} = opcell{oplen}; |
736 | 766 | else |
737 | 767 | if needvalidate |
738 | | - tempobj = jdict(); |
739 | | - tempobj.schema = obj.schema; |
740 | | - tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen); |
| 768 | + tempobj.currentpath__ = datapath; |
741 | 769 | le(tempobj, otherobj); |
742 | 770 | end |
743 | 771 | if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36)) |
|
839 | 867 | val = keys(obj); |
840 | 868 | end |
841 | 869 |
|
| 870 | + function val = isfield(obj, key) |
| 871 | + val = isKey(obj, key); |
| 872 | + end |
| 873 | + |
842 | 874 | % test if a key or index exists |
843 | 875 | function val = isKey(obj, key) |
844 | 876 | % list subfields at the current level |
|
851 | 883 | end |
852 | 884 | end |
853 | 885 |
|
| 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 | + |
854 | 900 | % return the number of subfields or array length |
855 | 901 | function val = len(obj) |
856 | 902 | % return the number of subfields at the current level |
|
937 | 983 | end |
938 | 984 |
|
939 | 985 | % set specified data attributes |
940 | | - function attr = setattr(obj, jsonpath, attrname, attrvalue) |
| 986 | + function attr = setattr(obj, datapath, attrname, attrvalue) |
941 | 987 | if (nargin == 3) |
942 | 988 | attrvalue = attrname; |
943 | | - attrname = jsonpath; |
944 | | - jsonpath = obj.currentpath__; |
| 989 | + attrname = datapath; |
| 990 | + datapath = obj.currentpath__; |
945 | 991 | 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(); |
948 | 994 | end |
949 | | - attrmap = obj.attr(jsonpath); |
| 995 | + attrmap = obj.attr(datapath); |
950 | 996 | attrmap(attrname) = attrvalue; |
951 | | - obj.attr(jsonpath) = attrmap; |
| 997 | + obj.attr(datapath) = attrmap; |
952 | 998 | attr = obj.attr; |
953 | 999 | end |
954 | 1000 |
|
955 | 1001 | % return specified data attributes, if not specified, list all attributes |
956 | | - function val = getattr(obj, jsonpath, attrname) |
| 1002 | + function val = getattr(obj, datapath, attrname) |
957 | 1003 | val = []; |
958 | 1004 | if (nargin == 1) |
959 | 1005 | if (~isKey(obj.attr, obj.currentpath__) && strcmp(obj.currentpath__, char(36))) |
|
964 | 1010 | return |
965 | 1011 | end |
966 | 1012 | 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__; |
970 | 1016 | else |
971 | 1017 | attrname = ''; |
972 | 1018 | end |
973 | 1019 | end |
974 | | - if (~isKey(obj.attr, jsonpath)) |
| 1020 | + if (~isKey(obj.attr, datapath)) |
975 | 1021 | return |
976 | 1022 | end |
977 | | - attrmap = obj.attr(jsonpath); |
| 1023 | + attrmap = obj.attr(datapath); |
978 | 1024 | if (isempty(attrname)) |
979 | 1025 | val = attrmap; |
980 | 1026 | elseif (isKey(attrmap, attrname)) |
|
0 commit comments