Skip to content

Commit 394904c

Browse files
committed
[feat] force casting data binary type when binType schema is defined
1 parent 464be7c commit 394904c

File tree

2 files changed

+163
-3
lines changed

2 files changed

+163
-3
lines changed

jdict.m

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,19 +591,25 @@
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
594598
if (needvalidate)
595599
tempobj = jdict();
596600
tempobj.setschema(obj.schema);
597-
datapath = buildpath_(obj.currentpath__, idxkey, oplen);
598601
end
599602

600603
% Fast path: single-level assignment like jd.key = value
601604
if (oplen == 1 && strcmp(idxkey(1).type, '.') && ischar(idxkey(1).subs))
602605
fieldname = idxkey(1).subs;
603606
% Skip if JSONPath
604607
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
605612
if needvalidate
606-
targetpath = [obj.currentpath__ '.' esckey_(fieldname)];
607613
tempobj.currentpath__ = targetpath;
608614
le(tempobj, otherobj);
609615
end
@@ -635,8 +641,11 @@
635641
% Fast path: single numeric index like jd.(1) = value
636642
if (oplen == 1 && strcmp(idxkey(1).type, '.') && isnumeric(idxkey(1).subs))
637643
% 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
638648
if needvalidate
639-
targetpath = [obj.currentpath__ '[' num2str(idxkey(1).subs - 1) ']'];
640649
tempobj.currentpath__ = targetpath;
641650
le(tempobj, otherobj);
642651
end
@@ -764,6 +773,9 @@
764773

765774
if (oplen >= 2 && ischar(idxkey(oplen - 1).subs) && strcmp(idxkey(oplen - 1).subs, 'v') && strcmp(idxkey(oplen).type, '()'))
766775
% Handle .v(index) = value at any depth
776+
if ~isempty(obj.schema)
777+
otherobj = castbintype_(otherobj, obj.schema, datapath, obj);
778+
end
767779
if needvalidate
768780
tempobj.currentpath__ = datapath;
769781
le(tempobj, otherobj);
@@ -782,13 +794,19 @@
782794
end
783795
opcell{oplen + 1} = opcell{oplen};
784796
elseif (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{oplen}))
797+
if ~isempty(obj.schema)
798+
otherobj = castbintype_(otherobj, obj.schema, datapath, obj);
799+
end
785800
if needvalidate
786801
tempobj.currentpath__ = datapath;
787802
le(tempobj, otherobj);
788803
end
789804
opcell{oplen}(idx.subs) = otherobj;
790805
opcell{oplen + 1} = opcell{oplen};
791806
else
807+
if ~isempty(obj.schema)
808+
otherobj = castbintype_(otherobj, obj.schema, datapath, obj);
809+
end
792810
if needvalidate
793811
tempobj.currentpath__ = datapath;
794812
le(tempobj, otherobj);
@@ -1460,3 +1478,26 @@
14601478
error('Coord "%s" not found in "%s"', mat2str(val), dim);
14611479
end
14621480
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: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,6 +2584,125 @@ function run_jsonlab_test(tests)
25842584
jd.setschema(struct('binType', 'logical'));
25852585
test_jsonlab('validate binType logical fail', @savejson, ~isempty(jd.validate()), '[true]', 'compact', 1);
25862586

2587+
% =======================================================================
2588+
% binType auto-coercion on = vs strict <= validation
2589+
% =======================================================================
2590+
2591+
% Test 1: = assignment auto-coerces double to uint8 (nested)
2592+
jd = jdict(struct('arr', []));
2593+
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);
2596+
2597+
% Test 2: = assignment auto-coerces double to int32
2598+
jd = jdict(struct('arr', []));
2599+
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);
2602+
2603+
% Test 3: = assignment auto-coerces double to single
2604+
jd = jdict(struct('arr', []));
2605+
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);
2608+
2609+
% Test 4: = assignment auto-coerces to logical
2610+
jd = jdict(struct('arr', []));
2611+
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);
2614+
2615+
% Test 5: = assignment auto-coerces to uint16
2616+
jd = jdict(struct('arr', []));
2617+
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);
2620+
2621+
% Test 6: = assignment auto-coerces to int64
2622+
jd = jdict(struct('arr', []));
2623+
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);
2626+
2627+
% Test 7: <= with correct type passes
2628+
jd = jdict(struct('arr', []));
2629+
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);
2637+
2638+
% Test 8: <= with wrong type fails
2639+
jd = jdict(struct('arr', []));
2640+
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);
2648+
2649+
% Test 9: <= with wrong type fails (int32)
2650+
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);
2659+
2660+
% Test 10: <= with wrong type fails (single)
2661+
jd = jdict(struct('arr', []));
2662+
jd.setschema(struct('type', 'object', 'properties', struct('arr', struct('binType', 'single'))));
2663+
try
2664+
jd.arr <= double([1.5, 2.5]);
2665+
lefail = false;
2666+
catch
2667+
lefail = true;
2668+
end
2669+
test_jsonlab('<= with double fails for single schema', @savejson, lefail, '[true]', 'compact', 1);
2670+
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);
2676+
2677+
% Test 12: Combined binType and minDims - = coerces type
2678+
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);
2682+
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))));
2686+
try
2687+
jd.arr <= int16([1, 2, 3, 4, 5]); % correct type, valid dims
2688+
lepass = true;
2689+
catch
2690+
lepass = false;
2691+
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);
2705+
25872706
% =======================================================================
25882707
% minDims validation
25892708
% =======================================================================

0 commit comments

Comments
 (0)