fix(json): Rectangle3DConverter loses a z-level on write
Write omitted z1/z2 whenever both z bounds sat at/outside the sbyte extremes, but Read reconstructs absent z as exactly z1=-128, z2=127 (depth 255). So any rectangle that tripped the omit condition without being that sentinel round-tripped to depth 255 — e.g. a homeRange-style z1=-128/z2=128 (depth 256) silently lost its top z-level on re-serialize. Omit z only for the exact -128/127 sentinel; write it otherwise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d8a64f3316
commit
15e77506c5
2 changed files with 50 additions and 1 deletions
|
|
@ -0,0 +1,46 @@
|
|||
using System.Text.Json;
|
||||
using Server.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Server.Tests.Json;
|
||||
|
||||
public class Rectangle3DConverterTests
|
||||
{
|
||||
private static Rectangle3D RoundTrip(Rectangle3D value)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value, JsonConfig.DefaultOptions);
|
||||
return JsonSerializer.Deserialize<Rectangle3D>(json, JsonConfig.DefaultOptions);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// homeRange-style full vertical range (z1=-128, depth 256 -> z2=128): must survive losslessly.
|
||||
[InlineData(100, 100, -128, 11, 11, 256)]
|
||||
// The omitted-z sentinel itself (z1=-128, z2=127 -> depth 255).
|
||||
[InlineData(100, 100, -128, 11, 11, 255)]
|
||||
// Ordinary bounds with a meaningful z.
|
||||
[InlineData(100, 100, 0, 11, 11, 5)]
|
||||
[InlineData(100, 100, 5, 11, 11, 122)] // z2 == 127 but z1 != -128
|
||||
[InlineData(100, 100, -128, 11, 11, 200)] // z1 == -128 but z2 != 127
|
||||
public void RoundTrips(int x, int y, int z, int w, int h, int d)
|
||||
{
|
||||
var rect = new Rectangle3D(x, y, z, w, h, d);
|
||||
Assert.Equal(rect, RoundTrip(rect));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullRangeSentinel_OmitsZ()
|
||||
{
|
||||
// z1=-128, z2=127 is the only case z is omitted.
|
||||
var json = JsonSerializer.Serialize(new Rectangle3D(0, 0, -128, 10, 10, 255), JsonConfig.DefaultOptions);
|
||||
Assert.DoesNotContain("z1", json);
|
||||
Assert.DoesNotContain("z2", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HomeRangeBounds_WritesZ()
|
||||
{
|
||||
var json = JsonSerializer.Serialize(new Rectangle3D(0, 0, -128, 10, 10, 256), JsonConfig.DefaultOptions);
|
||||
Assert.Contains("\"z1\":", json);
|
||||
Assert.Contains("\"z2\":", json);
|
||||
}
|
||||
}
|
||||
|
|
@ -175,7 +175,10 @@ public class Rectangle3DConverter : JsonConverter<Rectangle3D>
|
|||
|
||||
public override void Write(Utf8JsonWriter writer, Rectangle3D value, JsonSerializerOptions options)
|
||||
{
|
||||
var writeZ = value.Start.Z is > sbyte.MinValue and < sbyte.MaxValue || value.End.Z is > sbyte.MinValue and < sbyte.MaxValue;
|
||||
// Omit z only for the exact full-range sentinel DeserializeObj reconstructs when z is absent
|
||||
// (z1 == -128, z2 == 127). Any other z must be written, otherwise it round-trips to that
|
||||
// sentinel and is corrupted (e.g. a homeRange-style z1=-128/z2=128 would lose a z-level).
|
||||
var writeZ = value.Start.Z != sbyte.MinValue || value.End.Z != sbyte.MaxValue;
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("x1");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue