|
18 | 18 | % jd = jdict(data) wraps any matlab data (array, cell, struct, dictionary, ...) into a new jdict object |
19 | 19 | % jd = jdict(data, 'param1', value1, 'param2', value2, ...) use param/value pairs to initilize jd.flags |
20 | 20 | % jd = jdict(data, 'attr', attrmap) initilize data attributes using a containers.Map with JSONPath as keys |
| 21 | +% jd = jdict(data, 'schema', jschema) initilize data's JSON schema using a containers.Map object jschema |
21 | 22 | % |
22 | 23 | % member functions: |
23 | 24 | % jd.('cell1').v(i) or jd.('array1').v(2:3) returns specified elements if the element is a cell or array |
|
354 | 355 | i = i + 2; |
355 | 356 | continue |
356 | 357 | end |
357 | | - else |
358 | | - % single struct or scalar field access |
| 358 | + elseif isfield(val, onekey) |
| 359 | + % single struct with existing field |
359 | 360 | val = val.(onekey); |
360 | 361 | trackpath = [trackpath '.' escapedonekey]; |
| 362 | + else |
| 363 | + % field does not exist - return empty for <= assignment |
| 364 | + val = []; |
| 365 | + trackpath = [trackpath '.' escapedonekey]; |
361 | 366 | end |
362 | 367 | elseif (isa(val, 'containers.Map') || isa(val, 'dictionary')) |
363 | | - val = val(onekey); |
| 368 | + if isKey(val, onekey) |
| 369 | + val = val(onekey); |
| 370 | + else |
| 371 | + % key does not exist - return empty for <= assignment |
| 372 | + val = []; |
| 373 | + end |
364 | 374 | trackpath = [trackpath '.' escapedonekey]; |
365 | 375 | else |
366 | | - error('key name "%s" not found', onekey); |
| 376 | + % data is empty or other type - return empty for <= assignment |
| 377 | + val = []; |
| 378 | + trackpath = [trackpath '.' escapedonekey]; |
367 | 379 | end |
368 | 380 | else |
369 | 381 | error('method not supported'); |
|
512 | 524 | continue |
513 | 525 | end |
514 | 526 | if (ischar(idx.subs) && ~(~isempty(idx.subs) && idx.subs(1) == char(36))) |
| 527 | + % Handle empty or non-struct/map data |
| 528 | + if isempty(opcell{i}) || (~isstruct(opcell{i}) && ~isa(opcell{i}, 'containers.Map') && ~isa(opcell{i}, 'dictionary')) |
| 529 | + opcell{i} = obj.newkey_(); |
| 530 | + end |
515 | 531 | if (((isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) && ~isKey(opcell{i}, idx.subs))) |
516 | 532 | idx.type = '()'; |
517 | 533 | opcell{i}(idx.subs) = obj.newkey_(); |
|
594 | 610 | obj.data = opcell{1}; |
595 | 611 | end |
596 | 612 |
|
597 | | - % export data to json |
| 613 | + % export data to json, binary JSON, or other over a dozen formats |
598 | 614 | function val = tojson(obj, varargin) |
599 | 615 | % printing underlying data to compact-formed JSON string |
600 | | - val = obj.call_('savejson', '', obj, 'compact', 1, varargin{:}); |
| 616 | + val = obj.call_('savejd', '', obj, 'compact', 1, varargin{:}); |
601 | 617 | end |
602 | 618 |
|
603 | 619 | % load data from over a dozen data formats, including json and binary json |
604 | 620 | function obj = fromjson(obj, fname, varargin) |
605 | | - % loading diverse data files using loadjd interface in jsonlab |
606 | 621 | obj.data = obj.call_('loadjd', fname, varargin{:}); |
607 | 622 | end |
608 | 623 |
|
609 | 624 | function val = keys(obj) |
610 | | - % list subfields at the current level |
611 | 625 | if (isstruct(obj.data)) |
612 | | - val = fieldnames(obj.data); |
| 626 | + val = builtin('fieldnames', obj.data); |
613 | 627 | elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary')) |
614 | 628 | val = keys(obj.data); |
615 | 629 | else |
616 | 630 | val = 1:length(obj.data); |
617 | 631 | end |
| 632 | + val = val(:); |
| 633 | + end |
| 634 | + |
| 635 | + function val = fieldnames(obj) |
| 636 | + val = keys(obj); |
618 | 637 | end |
619 | 638 |
|
620 | 639 | % test if a key or index exists |
|
809 | 828 | error('No schema available. Use setschema() first or provide schema as argument.'); |
810 | 829 | end |
811 | 830 |
|
812 | | - subschema = jsonschema(obj.schema, [], 'getsubschema', obj.currentpath__); |
| 831 | + subschema = obj.call_('jsonschema', obj.schema, [], ... |
| 832 | + 'getsubschema', obj.currentpath__); |
813 | 833 |
|
814 | 834 | if isempty(subschema) |
815 | 835 | errors = {}; |
816 | 836 | return |
817 | 837 | end |
818 | 838 |
|
819 | | - [temp, errors] = jsonschema(obj.data, subschema, 'rootschema', obj.schema); |
| 839 | + [temp, errors] = obj.call_('jsonschema', obj.data, subschema, ... |
| 840 | + 'rootschema', obj.schema); |
820 | 841 | end |
821 | 842 |
|
822 | 843 | % convert attributes to JSON Schema |
|
937 | 958 | function result = le(obj, value) |
938 | 959 | % validate against schema if defined |
939 | 960 | if ~isempty(obj.schema) |
940 | | - subschema = jsonschema(obj.schema, [], 'getsubschema', obj.currentpath__); |
| 961 | + subschema = obj.call_('jsonschema', obj.schema, [], ... |
| 962 | + 'getsubschema', obj.currentpath__); |
941 | 963 |
|
942 | 964 | % if subschema found for this path, validate |
943 | 965 | if ~isempty(subschema) |
944 | | - [valid, errs] = jsonschema(value, subschema, 'rootschema', obj.schema); |
| 966 | + [valid, errs] = obj.call_('jsonschema', value, subschema, ... |
| 967 | + 'rootschema', obj.schema); |
945 | 968 | if ~valid |
946 | | - errmsg = sprintf('Schema validation failed for "%s":', obj.currentpath__); |
| 969 | + errmsg = sprintf('Schema validation failed for "%s":', ... |
| 970 | + obj.currentpath__); |
947 | 971 | for i = 1:length(errs) |
948 | 972 | errmsg = [errmsg ' ' errs{i} ';']; |
949 | 973 | end |
|
952 | 976 | end |
953 | 977 | end |
954 | 978 |
|
955 | | - % assign via root object using JSONPath |
| 979 | + % assign via root object |
956 | 980 | if strcmp(obj.currentpath__, char(36)) |
957 | 981 | obj.root__.data = value; |
958 | 982 | else |
959 | | - idx.type = '.'; |
960 | | - idx.subs = obj.currentpath__; |
961 | | - subsasgn(obj.root__, idx, value); |
| 983 | + % parse currentpath__ to build index keys |
| 984 | + % remove leading $. if present |
| 985 | + path = obj.currentpath__; |
| 986 | + if length(path) > 2 && path(1) == char(36) && path(2) == '.' |
| 987 | + path = path(3:end); |
| 988 | + elseif path(1) == char(36) |
| 989 | + path = path(2:end); |
| 990 | + end |
| 991 | + |
| 992 | + % split by unescaped dots |
| 993 | + parts = {}; |
| 994 | + current = ''; |
| 995 | + k = 1; |
| 996 | + while k <= length(path) |
| 997 | + if k < length(path) && path(k) == '\' && path(k + 1) == '.' |
| 998 | + current = [current '.']; |
| 999 | + k = k + 2; |
| 1000 | + elseif path(k) == '.' |
| 1001 | + if ~isempty(current) |
| 1002 | + parts{end + 1} = current; |
| 1003 | + end |
| 1004 | + current = ''; |
| 1005 | + k = k + 1; |
| 1006 | + else |
| 1007 | + current = [current path(k)]; |
| 1008 | + k = k + 1; |
| 1009 | + end |
| 1010 | + end |
| 1011 | + if ~isempty(current) |
| 1012 | + parts{end + 1} = current; |
| 1013 | + end |
| 1014 | + |
| 1015 | + % build idxkey array for subsasgn |
| 1016 | + idxkey = struct('type', {}, 'subs', {}); |
| 1017 | + for k = 1:length(parts) |
| 1018 | + idxkey(k).type = '.'; |
| 1019 | + idxkey(k).subs = parts{k}; |
| 1020 | + end |
| 1021 | + |
| 1022 | + % call subsasgn on root object |
| 1023 | + subsasgn(obj.root__, idxkey, value); |
962 | 1024 | end |
963 | | - result = obj; |
| 1025 | + result = obj.root__; |
964 | 1026 | end |
965 | 1027 |
|
966 | 1028 | end |
|
0 commit comments