Skip to content

Commit 677413f

Browse files
shimatclaude
andcommitted
Fix WriteJsonScalar for programmatically-created JsonValue numerics
JsonValue.Create(T) preserves the exact CLR type it was created from, so TryGetValue<T> does a strict type match against it - unlike a JsonNode.Parse()-produced JsonValue (backed by a flexible JsonElement that widens across numeric types). WriteJsonScalar only probed bool/ long/double/string, so a value created via JsonValue.Create(3) (int), JsonValue.Create(3f) (float), or a plain `new JsonObject { ["x"] = 3 }` assignment matched none of those checks and threw. Verified this empirically (JsonValue.Create(3)/Create(3.5f)/Create((short)3) each only satisfy TryGetValue of their own exact type) before fixing. Now probes every CLR numeric type JsonValue.Create supports (int, short, sbyte, byte, ushort, uint, ulong alongside the existing long; float, decimal alongside the existing double) and widens to the matching Write(long)/Write(double) overload. Added a regression test building a JsonObject via plain property assignment (not JsonNode.Parse) to cover this path, since the existing round-trip test's JsonNode.Parse-backed values didn't exercise it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 0fdeaf3 commit 677413f

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

src/OpenCvSharp/Modules/core/FileStorage.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,25 @@ public void Write(string name, JsonNode? value)
606606
private void WriteJsonScalar(string name, JsonValue value)
607607
{
608608
if (value.TryGetValue(out bool b)) { Write(name, b); return; }
609+
610+
// JsonValue.Create(T) preserves the exact CLR type it was created from - TryGetValue<T>
611+
// does a strict type match for it, unlike a JsonNode.Parse()-produced JsonValue (backed by
612+
// JsonElement, which freely widens across numeric types). So a value created via e.g.
613+
// JsonValue.Create(3) (int) or a plain `new JsonObject { ["x"] = 3 }` assignment fails
614+
// TryGetValue<long>/<double> and must be probed by its exact CLR type instead.
609615
if (value.TryGetValue(out long l)) { Write(name, l); return; }
616+
if (value.TryGetValue(out int i)) { Write(name, (long)i); return; }
617+
if (value.TryGetValue(out short sh)) { Write(name, (long)sh); return; }
618+
if (value.TryGetValue(out sbyte sb)) { Write(name, (long)sb); return; }
619+
if (value.TryGetValue(out byte by)) { Write(name, (long)by); return; }
620+
if (value.TryGetValue(out ushort us)) { Write(name, (long)us); return; }
621+
if (value.TryGetValue(out uint ui)) { Write(name, (long)ui); return; }
622+
if (value.TryGetValue(out ulong ul)) { Write(name, unchecked((long)ul)); return; }
623+
610624
if (value.TryGetValue(out double d)) { Write(name, d); return; }
625+
if (value.TryGetValue(out float f)) { Write(name, (double)f); return; }
626+
if (value.TryGetValue(out decimal dec)) { Write(name, (double)dec); return; }
627+
611628
if (value.TryGetValue(out string? s)) { Write(name, s!); return; }
612629
throw new NotSupportedException($"Unsupported JsonValue underlying type for key '{name}'.");
613630
}

test/OpenCvSharp.Tests/core/FileStorageTest.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,46 @@ public void WriteJsonNodeNullThrows()
654654
Assert.Throws<NotSupportedException>(() => fs.Write("x", (JsonNode?)null));
655655
}
656656

657+
[Fact]
658+
public void WriteJsonNodeHandlesProgrammaticNumericJsonValueTypes()
659+
{
660+
// Unlike a JsonNode.Parse()-produced value (backed by a flexible JsonElement),
661+
// JsonValue.Create(T) - what a plain `new JsonObject { ["x"] = 3 }` assignment does under
662+
// the hood - preserves the exact CLR type it was created from, so this must be covered
663+
// independently of the JsonNode.Parse-based round-trip test above.
664+
const string fileName = "fs_write_json_node_numeric_types.yml";
665+
666+
var obj = new JsonObject
667+
{
668+
["intValue"] = 3,
669+
["longValue"] = 9_000_000_000L,
670+
["shortValue"] = (short)7,
671+
["byteValue"] = (byte)8,
672+
["floatValue"] = 1.5f,
673+
["doubleValue"] = 2.5,
674+
["boolValue"] = true,
675+
["stringValue"] = "text",
676+
};
677+
678+
using (var fs = new FileStorage(fileName, FileStorage.Modes.Write))
679+
{
680+
foreach (var (key, value) in obj)
681+
fs.Write(key, value);
682+
}
683+
684+
using (var fs = new FileStorage(fileName, FileStorage.Modes.Read))
685+
{
686+
Assert.Equal(3, fs["intValue"]!.ReadInt());
687+
Assert.Equal(9_000_000_000L, fs["longValue"]!.ReadInt64());
688+
Assert.Equal(7, fs["shortValue"]!.ReadInt());
689+
Assert.Equal(8, fs["byteValue"]!.ReadInt());
690+
Assert.Equal(1.5, fs["floatValue"]!.ReadDouble(), 3);
691+
Assert.Equal(2.5, fs["doubleValue"]!.ReadDouble(), 3);
692+
Assert.True(fs["boolValue"]!.ReadInt() != 0);
693+
Assert.Equal("text", fs["stringValue"]!.ReadString());
694+
}
695+
}
696+
657697
[Fact]
658698
public void GetPathNavigatesNestedStructure()
659699
{

0 commit comments

Comments
 (0)