Skip to content

Commit 83ff89b

Browse files
committed
add post about number equality in json
1 parent 1dca41d commit 83ff89b

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed

_posts/2023/2023-07-26-jsonnode-odd-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "JsonNode's Odd API"
33
date: 2023-07-26 09:00:00 +1200
4-
tags: [json-path, json-pointer]
4+
tags: [json-path, json-pointer, json-node, oddity]
55
toc: true
66
pin: false
77
---
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
title: ".Net Decimals are Weird"
3+
date: 2023-11-14 09:00:00 +1200
4+
tags: [json-node, oddity]
5+
toc: true
6+
pin: false
7+
---
8+
9+
I've discovered another odd consequence of what is probably fully intentional code: `4m != 4.0m`.
10+
11+
Okay, that's not strictly true, but it does seem so if you're comparing the values in JSON.
12+
13+
```C#
14+
var a = 4m;
15+
var b = 4.0m;
16+
17+
JsonNode jsonA = a;
18+
JsonNOde jsonB = b;
19+
20+
// use .IsEquivalentTo() from Json.More.Net
21+
Assert.True(jsonA.IsEquivalentTo(jsonB)); // fails!
22+
```
23+
24+
What?!
25+
26+
_This took me so long to find..._
27+
28+
## What's happening ([brother](https://www.youtube.com/watch?v=tvjrSU9RaPs))
29+
30+
The main insight is contained in [this StackOverflow answer](https://stackoverflow.com/a/13770183/878701). `decimal` has the ability to retain significant digits! Even if those digits are expressed in code!!
31+
32+
So when we type `4.0m` in C# code, the compiler tells `System.Decimal` that the `.0` is important. When the value is printed (e.g. via `.ToString()`), even without specifying a format, you get `4.0` back. And this includes when serializing to JSON. If you debug the code above, you'll see that `a` has a value of `4` while `b` has a value of `4.0`. Even before it gets to the `JsonNode` assignments.
33+
34+
While this doesn't affect _numeric_ equality, it could affect equality that relies on the string representation of the number (like in JSON).
35+
36+
## How this bit me
37+
38+
In developing a new library for [JSON-e](https://json-e.js.org/) support (spoiler, I guess), I found a test that was failing, and I couldn't understand why.
39+
40+
I won't go into the full details here, but JSON-e supports expressions, and one of the tests has the expression `4 == 3.2 + 0.8`. Simple enough, right? So why was I failing this?
41+
42+
When getting numbers from JSON throughout all of my libraries, I chose to use `decimal` because I felt it was more important to support JSON's arbitrary precision with `decimal`'s higher precision rather than using `double` for a bit more range. So when parsing the above expression, I get a tree that looks like this:
43+
44+
```
45+
==
46+
/ \
47+
4 +
48+
/ \
49+
3.2 0.8
50+
```
51+
52+
where each of the numbers are represented as `JsonNode`s with `decimals` underneath.
53+
54+
When the system processes `3.2 + 0.8`, it gives me `4.0`. As I said before, numeric comparisons between `decimal`s work fine. But in these expressions, `==` doesn't compare just numbers; it compares `JsonNode`s. And it does so using my `.IsEquivalentTo()` extension method.
55+
56+
## What's wrong with the extension?
57+
58+
When I built the extension method, I already had one for `JsonElement`. (It handles everything correctly, too.) However `JsonNode` doesn't always store `JsonElement` underneath. It can also store the raw value.
59+
60+
This has an interesting nuance to the problem in that if the `JsonNode`s are parsed:
61+
62+
```C#
63+
var jsonA = JsonNode.Parse("4");
64+
var jsonB = JsonNode.Parse("4.0");
65+
66+
Assert.True(jsonA.IsEquivalentTo(jsonB));
67+
```
68+
69+
the assertion passes because parsing into `JsonNode` just stores `JsonElement`, and the comparison works for that.
70+
71+
So instead of rehashing all of the possibilities of checking strings, booleans, and all of the various numeric types, I figured it'd be simple enough to just `.ToString()` the node and compare the output.
72+
73+
And it worked... until I tried the expression above. For **18 months** it's worked without any problems. Such is software development, I suppose.
74+
75+
## It's fixed now
76+
77+
So now I check explicitly for numeric equality by calling `.GetNumber()`, which checks all of the various .Net number types returns a `decimal?` (null if it's not a number).
78+
79+
There's a new package available for those impacted by this (I didn't receive any reports).
80+
81+
And that's the story of how creating a new package to support a new JSON functionality showed me how 4 is not always 4.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: "Why I'm Updating My JSON Schema Vocabularies"
3+
date: 2023-11-09 09:00:00 +1200
4+
tags: [json-schema, vocab, vocabulary]
5+
toc: true
6+
pin: false
7+
---

0 commit comments

Comments
 (0)