Skip to content

Commit cf5098d

Browse files
committed
[schema] support creating new keys when using <= operator
1 parent eecdde8 commit cf5098d

File tree

1 file changed

+81
-19
lines changed

1 file changed

+81
-19
lines changed

jdict.m

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
% jd = jdict(data) wraps any matlab data (array, cell, struct, dictionary, ...) into a new jdict object
1919
% jd = jdict(data, 'param1', value1, 'param2', value2, ...) use param/value pairs to initilize jd.flags
2020
% 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
2122
%
2223
% member functions:
2324
% jd.('cell1').v(i) or jd.('array1').v(2:3) returns specified elements if the element is a cell or array
@@ -354,16 +355,27 @@
354355
i = i + 2;
355356
continue
356357
end
357-
else
358-
% single struct or scalar field access
358+
elseif isfield(val, onekey)
359+
% single struct with existing field
359360
val = val.(onekey);
360361
trackpath = [trackpath '.' escapedonekey];
362+
else
363+
% field does not exist - return empty for <= assignment
364+
val = [];
365+
trackpath = [trackpath '.' escapedonekey];
361366
end
362367
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
364374
trackpath = [trackpath '.' escapedonekey];
365375
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];
367379
end
368380
else
369381
error('method not supported');
@@ -512,6 +524,10 @@
512524
continue
513525
end
514526
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
515531
if (((isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) && ~isKey(opcell{i}, idx.subs)))
516532
idx.type = '()';
517533
opcell{i}(idx.subs) = obj.newkey_();
@@ -594,27 +610,30 @@
594610
obj.data = opcell{1};
595611
end
596612

597-
% export data to json
613+
% export data to json, binary JSON, or other over a dozen formats
598614
function val = tojson(obj, varargin)
599615
% 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{:});
601617
end
602618

603619
% load data from over a dozen data formats, including json and binary json
604620
function obj = fromjson(obj, fname, varargin)
605-
% loading diverse data files using loadjd interface in jsonlab
606621
obj.data = obj.call_('loadjd', fname, varargin{:});
607622
end
608623

609624
function val = keys(obj)
610-
% list subfields at the current level
611625
if (isstruct(obj.data))
612-
val = fieldnames(obj.data);
626+
val = builtin('fieldnames', obj.data);
613627
elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary'))
614628
val = keys(obj.data);
615629
else
616630
val = 1:length(obj.data);
617631
end
632+
val = val(:);
633+
end
634+
635+
function val = fieldnames(obj)
636+
val = keys(obj);
618637
end
619638

620639
% test if a key or index exists
@@ -809,14 +828,16 @@
809828
error('No schema available. Use setschema() first or provide schema as argument.');
810829
end
811830

812-
subschema = jsonschema(obj.schema, [], 'getsubschema', obj.currentpath__);
831+
subschema = obj.call_('jsonschema', obj.schema, [], ...
832+
'getsubschema', obj.currentpath__);
813833

814834
if isempty(subschema)
815835
errors = {};
816836
return
817837
end
818838

819-
[temp, errors] = jsonschema(obj.data, subschema, 'rootschema', obj.schema);
839+
[temp, errors] = obj.call_('jsonschema', obj.data, subschema, ...
840+
'rootschema', obj.schema);
820841
end
821842

822843
% convert attributes to JSON Schema
@@ -937,13 +958,16 @@
937958
function result = le(obj, value)
938959
% validate against schema if defined
939960
if ~isempty(obj.schema)
940-
subschema = jsonschema(obj.schema, [], 'getsubschema', obj.currentpath__);
961+
subschema = obj.call_('jsonschema', obj.schema, [], ...
962+
'getsubschema', obj.currentpath__);
941963

942964
% if subschema found for this path, validate
943965
if ~isempty(subschema)
944-
[valid, errs] = jsonschema(value, subschema, 'rootschema', obj.schema);
966+
[valid, errs] = obj.call_('jsonschema', value, subschema, ...
967+
'rootschema', obj.schema);
945968
if ~valid
946-
errmsg = sprintf('Schema validation failed for "%s":', obj.currentpath__);
969+
errmsg = sprintf('Schema validation failed for "%s":', ...
970+
obj.currentpath__);
947971
for i = 1:length(errs)
948972
errmsg = [errmsg ' ' errs{i} ';'];
949973
end
@@ -952,15 +976,53 @@
952976
end
953977
end
954978

955-
% assign via root object using JSONPath
979+
% assign via root object
956980
if strcmp(obj.currentpath__, char(36))
957981
obj.root__.data = value;
958982
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);
9621024
end
963-
result = obj;
1025+
result = obj.root__;
9641026
end
9651027

9661028
end

0 commit comments

Comments
 (0)