Skip to content

Commit 1ec43da

Browse files
committed
[feat] perform auto-casting when using <=
1 parent 394904c commit 1ec43da

File tree

2 files changed

+75
-120
lines changed

2 files changed

+75
-120
lines changed

jdict.m

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -591,25 +591,19 @@
591591
end
592592
% check if kind-validation is needed
593593
needvalidate = (~isempty(obj.schema) && ~isempty(kindval));
594-
% always compute datapath if schema exists (for casting)
595-
if ~isempty(obj.schema)
596-
datapath = buildpath_(obj.currentpath__, idxkey, oplen);
597-
end
598594
if (needvalidate)
599595
tempobj = jdict();
600596
tempobj.setschema(obj.schema);
597+
datapath = buildpath_(obj.currentpath__, idxkey, oplen);
601598
end
602599

603600
% Fast path: single-level assignment like jd.key = value
604601
if (oplen == 1 && strcmp(idxkey(1).type, '.') && ischar(idxkey(1).subs))
605602
fieldname = idxkey(1).subs;
606603
% Skip if JSONPath
607604
if (isempty(fieldname) || fieldname(1) ~= char(36))
608-
targetpath = [obj.currentpath__ '.' esckey_(fieldname)];
609-
if ~isempty(obj.schema)
610-
otherobj = castbintype_(otherobj, obj.schema, targetpath, obj);
611-
end
612605
if needvalidate
606+
targetpath = [obj.currentpath__ '.' esckey_(fieldname)];
613607
tempobj.currentpath__ = targetpath;
614608
le(tempobj, otherobj);
615609
end
@@ -641,11 +635,8 @@
641635
% Fast path: single numeric index like jd.(1) = value
642636
if (oplen == 1 && strcmp(idxkey(1).type, '.') && isnumeric(idxkey(1).subs))
643637
% validate if kind is set
644-
targetpath = [obj.currentpath__ '[' num2str(idxkey(1).subs - 1) ']'];
645-
if ~isempty(obj.schema)
646-
otherobj = castbintype_(otherobj, obj.schema, targetpath, obj);
647-
end
648638
if needvalidate
639+
targetpath = [obj.currentpath__ '[' num2str(idxkey(1).subs - 1) ']'];
649640
tempobj.currentpath__ = targetpath;
650641
le(tempobj, otherobj);
651642
end
@@ -773,9 +764,6 @@
773764

774765
if (oplen >= 2 && ischar(idxkey(oplen - 1).subs) && strcmp(idxkey(oplen - 1).subs, 'v') && strcmp(idxkey(oplen).type, '()'))
775766
% Handle .v(index) = value at any depth
776-
if ~isempty(obj.schema)
777-
otherobj = castbintype_(otherobj, obj.schema, datapath, obj);
778-
end
779767
if needvalidate
780768
tempobj.currentpath__ = datapath;
781769
le(tempobj, otherobj);
@@ -794,19 +782,13 @@
794782
end
795783
opcell{oplen + 1} = opcell{oplen};
796784
elseif (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{oplen}))
797-
if ~isempty(obj.schema)
798-
otherobj = castbintype_(otherobj, obj.schema, datapath, obj);
799-
end
800785
if needvalidate
801786
tempobj.currentpath__ = datapath;
802787
le(tempobj, otherobj);
803788
end
804789
opcell{oplen}(idx.subs) = otherobj;
805790
opcell{oplen + 1} = opcell{oplen};
806791
else
807-
if ~isempty(obj.schema)
808-
otherobj = castbintype_(otherobj, obj.schema, datapath, obj);
809-
end
810792
if needvalidate
811793
tempobj.currentpath__ = datapath;
812794
le(tempobj, otherobj);
@@ -1250,6 +1232,17 @@
12501232

12511233
% if subschema found for this path, validate
12521234
if ~isempty(subschema)
1235+
% forcing type when binType is defined
1236+
if isa(subschema, 'containers.Map') && isKey(subschema, 'binType')
1237+
bintype = subschema('binType');
1238+
if ~isa(value, bintype)
1239+
try
1240+
value = cast(value, bintype);
1241+
catch
1242+
end
1243+
end
1244+
end
1245+
12531246
[valid, errs] = obj.call_('jsonschema', value, subschema, ...
12541247
'rootschema', obj.schema);
12551248
if ~valid
@@ -1478,26 +1471,3 @@
14781471
error('Coord "%s" not found in "%s"', mat2str(val), dim);
14791472
end
14801473
end
1481-
1482-
% auto-coerce value to match binType in schema (for = assignment only)
1483-
function val = castbintype_(val, schema, path, obj)
1484-
if isempty(schema)
1485-
return
1486-
end
1487-
try
1488-
subschema = obj.call_('jsonschema', schema, [], 'getsubschema', path);
1489-
catch
1490-
return
1491-
end
1492-
if isempty(subschema) || ~isa(subschema, 'containers.Map') || ~isKey(subschema, 'binType')
1493-
return
1494-
end
1495-
bintype = subschema('binType');
1496-
if isa(val, bintype)
1497-
return
1498-
end
1499-
try
1500-
val = cast(val, bintype);
1501-
catch
1502-
end
1503-
end

test/run_jsonlab_test.m

Lines changed: 61 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,123 +2585,108 @@ function run_jsonlab_test(tests)
25852585
test_jsonlab('validate binType logical fail', @savejson, ~isempty(jd.validate()), '[true]', 'compact', 1);
25862586

25872587
% =======================================================================
2588-
% binType auto-coercion on = vs strict <= validation
2588+
% binType auto-casting on <= operator
25892589
% =======================================================================
25902590

2591-
% Test 1: = assignment auto-coerces double to uint8 (nested)
2591+
% Test 1: <= auto-cast double to uint8
25922592
jd = jdict(struct('arr', []));
25932593
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))));
2594-
jd.arr = [1, 2, 3]; % double should be coerced to uint8
2595-
test_jsonlab('= coerces double to uint8', @savejson, isa(jd.arr.v(), 'uint8'), '[true]', 'compact', 1);
2594+
jd.arr <= [1, 2, 3];
2595+
test_jsonlab('<= casts double to uint8', @savejson, isa(jd.arr.v(), 'uint8'), '[true]', 'compact', 1);
25962596

2597-
% Test 2: = assignment auto-coerces double to int32
2597+
% Test 2: <= auto-cast double to int32
25982598
jd = jdict(struct('arr', []));
25992599
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int32'))));
2600-
jd.arr = [-100, 0, 100]; % double should be coerced to int32
2601-
test_jsonlab('= coerces double to int32', @savejson, isa(jd.arr.v(), 'int32'), '[true]', 'compact', 1);
2600+
jd.arr <= [-100, 0, 100];
2601+
test_jsonlab('<= casts double to int32', @savejson, isa(jd.arr.v(), 'int32'), '[true]', 'compact', 1);
26022602

2603-
% Test 3: = assignment auto-coerces double to single
2603+
% Test 3: <= auto-cast double to single
26042604
jd = jdict(struct('arr', []));
26052605
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'single'))));
2606-
jd.arr = [1.5, 2.5, 3.5]; % double should be coerced to single
2607-
test_jsonlab('= coerces double to single', @savejson, isa(jd.arr.v(), 'single'), '[true]', 'compact', 1);
2606+
jd.arr <= [1.5, 2.5, 3.5];
2607+
test_jsonlab('<= casts double to single', @savejson, isa(jd.arr.v(), 'single'), '[true]', 'compact', 1);
26082608

2609-
% Test 4: = assignment auto-coerces to logical
2609+
% Test 4: <= auto-cast to logical
26102610
jd = jdict(struct('arr', []));
26112611
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'logical'))));
2612-
jd.arr = [1, 0, 1]; % double should be coerced to logical
2613-
test_jsonlab('= coerces double to logical', @savejson, isa(jd.arr.v(), 'logical'), '[true]', 'compact', 1);
2612+
jd.arr <= [1, 0, 1];
2613+
test_jsonlab('<= casts double to logical', @savejson, isa(jd.arr.v(), 'logical'), '[true]', 'compact', 1);
26142614

2615-
% Test 5: = assignment auto-coerces to uint16
2615+
% Test 5: <= auto-cast to uint16
26162616
jd = jdict(struct('arr', []));
26172617
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint16'))));
2618-
jd.arr = [100, 200, 300];
2619-
test_jsonlab('= coerces double to uint16', @savejson, isa(jd.arr.v(), 'uint16'), '[true]', 'compact', 1);
2618+
jd.arr <= [100, 200, 300];
2619+
test_jsonlab('<= casts double to uint16', @savejson, isa(jd.arr.v(), 'uint16'), '[true]', 'compact', 1);
26202620

2621-
% Test 6: = assignment auto-coerces to int64
2621+
% Test 6: <= auto-cast to int64
26222622
jd = jdict(struct('arr', []));
26232623
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int64'))));
2624-
jd.arr = [1e10, -1e10];
2625-
test_jsonlab('= coerces double to int64', @savejson, isa(jd.arr.v(), 'int64'), '[true]', 'compact', 1);
2624+
jd.arr <= [1e10, -1e10];
2625+
test_jsonlab('<= casts double to int64', @savejson, isa(jd.arr.v(), 'int64'), '[true]', 'compact', 1);
26262626

2627-
% Test 7: <= with correct type passes
2627+
% Test 7: <= with already correct type passes
26282628
jd = jdict(struct('arr', []));
26292629
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))));
2630-
try
2631-
jd.arr <= uint8([1, 2, 3]);
2632-
lepass = true;
2633-
catch
2634-
lepass = false;
2635-
end
2636-
test_jsonlab('<= with correct uint8 passes', @savejson, lepass, '[true]', 'compact', 1);
2630+
jd.arr <= uint8([1, 2, 3]);
2631+
test_jsonlab('<= with correct uint8 unchanged', @savejson, isequal(jd.arr.v(), uint8([1, 2, 3])), '[true]', 'compact', 1);
26372632

2638-
% Test 8: <= with wrong type fails
2633+
% Test 8: <= casts int8 to uint8
26392634
jd = jdict(struct('arr', []));
26402635
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))));
2641-
try
2642-
jd.arr <= double([1, 2, 3]); % should fail - no coercion on <=
2643-
lefail = false;
2644-
catch
2645-
lefail = true;
2646-
end
2647-
test_jsonlab('<= with double fails for uint8 schema', @savejson, lefail, '[true]', 'compact', 1);
2636+
jd.arr <= int8([1, 2, 3]);
2637+
test_jsonlab('<= casts int8 to uint8', @savejson, isa(jd.arr.v(), 'uint8'), '[true]', 'compact', 1);
26482638

2649-
% Test 9: <= with wrong type fails (int32)
2639+
% Test 9: <= combined binType and minDims
26502640
jd = jdict(struct('arr', []));
2651-
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int32'))));
2652-
try
2653-
jd.arr <= double([1, 2, 3]);
2654-
lefail = false;
2655-
catch
2656-
lefail = true;
2657-
end
2658-
test_jsonlab('<= with double fails for int32 schema', @savejson, lefail, '[true]', 'compact', 1);
2641+
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int16', 'minDims', 3))));
2642+
jd.arr <= [1, 2, 3, 4, 5];
2643+
test_jsonlab('<= casts with binType+minDims', @savejson, isa(jd.arr.v(), 'int16'), '[true]', 'compact', 1);
26592644

2660-
% Test 10: <= with wrong type fails (single)
2645+
% Test 10: <= fails minDims after casting
26612646
jd = jdict(struct('arr', []));
2662-
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'single'))));
2647+
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int16', 'minDims', 10))));
26632648
try
2664-
jd.arr <= double([1.5, 2.5]);
2649+
jd.arr <= [1, 2, 3]; % casts to int16, but fails minDims
26652650
lefail = false;
26662651
catch
26672652
lefail = true;
26682653
end
2669-
test_jsonlab('<= with double fails for single schema', @savejson, lefail, '[true]', 'compact', 1);
2654+
test_jsonlab('<= casts then fails minDims', @savejson, lefail, '[true]', 'compact', 1);
26702655

2671-
% Test 11: Correct type on = does not change value
2672-
jd = jdict(struct('arr', []));
2673-
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))));
2674-
jd.arr = uint8([5, 6, 7]);
2675-
test_jsonlab('= with correct type unchanged', @savejson, isequal(jd.arr.v(), uint8([5, 6, 7])), '[true]', 'compact', 1);
2656+
% Test 11: Deep nested structure with binType
2657+
jd = jdict(struct('level1', struct('level2', struct('arr', []))));
2658+
jd.setschema(struct('type', 'object', 'properties', struct('level1', struct('type', 'object', 'properties', struct('level2', struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))))))));
2659+
jd.level1.level2.arr <= [10, 20, 30];
2660+
test_jsonlab('deep nested <= casts to uint8', @savejson, isa(jd.level1.level2.arr.v(), 'uint8'), '[true]', 'compact', 1);
26762661

2677-
% Test 12: Combined binType and minDims - = coerces type
2662+
% Test 12: = does NOT cast (stays double)
26782663
jd = jdict(struct('arr', []));
2679-
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int16', 'minDims', 3))));
2680-
jd.arr = [1, 2, 3, 4, 5]; % coerce to int16
2681-
test_jsonlab('= coerces with binType+minDims', @savejson, isa(jd.arr.v(), 'int16'), '[true]', 'compact', 1);
2664+
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))));
2665+
jd.arr = [1, 2, 3];
2666+
test_jsonlab('= does not cast (stays double)', @savejson, isa(jd.arr.v(), 'double'), '[true]', 'compact', 1);
26822667

2683-
% Test 13: Combined binType and minDims - <= strict
2684-
jd = jdict(struct('arr', []));
2685-
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'int16', 'minDims', 3))));
2668+
% Test 13: Root level <= casts
2669+
jd = jdict();
2670+
jd.setschema(struct('binType', 'uint8'));
2671+
jd <= [5, 6, 7];
2672+
test_jsonlab('root <= casts to uint8', @savejson, isa(jd.v(), 'uint8'), '[true]', 'compact', 1);
2673+
2674+
% Test 14: <= with 2D array casts
2675+
jd = jdict(struct('mat', []));
2676+
jd.setschema(struct('type', 'object', 'properties', struct('mat', struct('binType', 'single'))));
2677+
jd.mat <= [1, 2; 3, 4];
2678+
test_jsonlab('<= casts 2D to single', @savejson, isa(jd.mat.v(), 'single'), '[true]', 'compact', 1);
2679+
2680+
% Test 15: <= validates other schema rules after casting
2681+
jd = jdict(struct('val', []));
2682+
jd.setschema(struct('type', 'object', 'properties', struct('val', struct('type', 'integer', 'minimum', 0, 'maximum', 100))));
26862683
try
2687-
jd.arr <= int16([1, 2, 3, 4, 5]); % correct type, valid dims
2688-
lepass = true;
2684+
jd.val <= 150; % should fail maximum check
2685+
lefail = false;
26892686
catch
2690-
lepass = false;
2687+
lefail = true;
26912688
end
2692-
test_jsonlab('<= passes with correct type and dims', @savejson, lepass, '[true]', 'compact', 1);
2693-
2694-
% Test 14: Deep nested structure with binType
2695-
jd = jdict(struct('level1', struct('level2', struct('arr', []))));
2696-
jd.setschema(struct('type', 'object', 'properties', struct('level1', struct('type', 'object', 'properties', struct('level2', struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))))))));
2697-
jd.level1.level2.arr = [10, 20, 30];
2698-
test_jsonlab('deep nested = coerces to uint8', @savejson, isa(jd.level1.level2.arr.v(), 'uint8'), '[true]', 'compact', 1);
2699-
2700-
% Test 15: = coerces int8 to uint8
2701-
jd = jdict(struct('arr', []));
2702-
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'uint8'))));
2703-
jd.arr = int8([1, 2, 3]); % int8 should be coerced to uint8
2704-
test_jsonlab('= coerces int8 to uint8', @savejson, isa(jd.arr.v(), 'uint8'), '[true]', 'compact', 1);
2689+
test_jsonlab('<= validates non-binType schema', @savejson, lefail, '[true]', 'compact', 1);
27052690

27062691
% =======================================================================
27072692
% minDims validation

0 commit comments

Comments
 (0)