Skip to content

Commit 75665e8

Browse files
committed
Fix JSON serialization of quantities with decimal values
Add IDecimalQuantity interface to expose the decimal value Serialize decimal values as string to keep number of decimal places
1 parent 9070996 commit 75665e8

File tree

10 files changed

+229
-66
lines changed

10 files changed

+229
-66
lines changed

CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,15 @@ namespace UnitsNet
6969
/// {_quantity.XmlDocRemarks}
7070
/// </remarks>");
7171

72+
Writer.W(@$"
73+
public partial struct {_quantity.Name} : IQuantity<{_unitEnumName}>, ");
74+
if (_quantity.BaseType == "decimal")
75+
{
76+
Writer.W("IDecimalQuantity, ");
77+
}
78+
79+
Writer.WL($"IEquatable<{_quantity.Name}>, IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable");
7280
Writer.WL($@"
73-
public partial struct {_quantity.Name} : IQuantity<{_unitEnumName}>, IEquatable<{_quantity.Name}>, IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable
7481
{{
7582
/// <summary>
7683
/// The numeric value this quantity was constructed with.
@@ -268,6 +275,11 @@ private void GenerateProperties()
268275
Writer.WL(@"
269276
double IQuantity.Value => (double) _value;
270277
");
278+
if (_quantity.BaseType == "decimal")
279+
Writer.WL(@"
280+
/// <inheritdoc cref=""IDecimalQuantity.Value""/>
281+
decimal IDecimalQuantity.Value => _value;
282+
");
271283

272284
Writer.WL($@"
273285
Enum IQuantity.Unit => Unit;

UnitsNet.Serialization.JsonNet.Tests/UnitsNetBaseJsonConverterTest.cs

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Globalization;
67
using Newtonsoft.Json;
78
using Newtonsoft.Json.Converters;
89
using Newtonsoft.Json.Linq;
@@ -13,38 +14,47 @@ namespace UnitsNet.Serialization.JsonNet.Tests
1314
{
1415
public sealed class UnitsNetBaseJsonConverterTest
1516
{
16-
private TestConverter _sut;
17+
private readonly TestConverter _sut;
1718

1819
public UnitsNetBaseJsonConverterTest()
1920
{
2021
_sut = new TestConverter();
2122
}
2223

2324
[Fact]
24-
public void UnitsNetBaseJsonConverter_ConvertIQuantity_works_as_expected()
25+
public void UnitsNetBaseJsonConverter_ConvertIQuantity_works_with_double_type()
2526
{
26-
var result = _sut.Test_ConvertIQuantity(Power.FromWatts(10.2365D));
27+
var result = _sut.Test_ConvertDoubleIQuantity(Length.FromMeters(10.2365));
28+
29+
Assert.Equal("LengthUnit.Meter", result.Unit);
30+
Assert.Equal(10.2365, result.Value);
31+
}
32+
33+
[Fact]
34+
public void UnitsNetBaseJsonConverter_ConvertIQuantity_works_with_decimal_type()
35+
{
36+
var result = _sut.Test_ConvertDecimalIQuantity(Power.FromWatts(10.2365m));
2737

2838
Assert.Equal("PowerUnit.Watt", result.Unit);
29-
Assert.Equal(10.2365D, result.Value);
39+
Assert.Equal(10.2365m, result.Value);
3040
}
3141

3242
[Fact]
3343
public void UnitsNetBaseJsonConverter_ConvertIQuantity_throws_ArgumentNullException_when_quantity_is_NULL()
3444
{
35-
var result = Assert.Throws<ArgumentNullException>(() => _sut.Test_ConvertIQuantity(null));
45+
var result = Assert.Throws<ArgumentNullException>(() => _sut.Test_ConvertDoubleIQuantity(null));
3646

3747
Assert.Equal("Value cannot be null.\r\nParameter name: quantity", result.Message);
3848
}
3949

4050
[Fact]
4151
public void UnitsNetBaseJsonConverter_ConvertValueUnit_works_as_expected()
4252
{
43-
var result = _sut.Test_ConvertValueUnit("PowerUnit.Watt", 10.2365D);
53+
var result = _sut.Test_ConvertDecimalValueUnit("PowerUnit.Watt", 10.2365m);
4454

4555
Assert.NotNull(result);
4656
Assert.IsType<Power>(result);
47-
Assert.True(Power.FromWatts(10.2365D).Equals((Power)result, 1E-5, ComparisonType.Absolute));
57+
Assert.True(Power.FromWatts(10.2365m).Equals((Power)result, 1E-5, ComparisonType.Absolute));
4858

4959
}
5060

@@ -59,7 +69,7 @@ public void UnitsNetBaseJsonConverter_ConvertValueUnit_works_with_NULL_value()
5969
[Fact]
6070
public void UnitsNetBaseJsonConverter_ConvertValueUnit_throws_UnitsNetException_when_unit_does_not_exist()
6171
{
62-
var result = Assert.Throws<UnitsNetException>(() => _sut.Test_ConvertValueUnit("SomeImaginaryUnit.Watt", 10.2365D));
72+
var result = Assert.Throws<UnitsNetException>(() => _sut.Test_ConvertDoubleValueUnit("SomeImaginaryUnit.Watt", 10.2365D));
6373

6474
Assert.Equal("Unable to find enum type.", result.Message);
6575
Assert.True(result.Data.Contains("type"));
@@ -69,7 +79,7 @@ public void UnitsNetBaseJsonConverter_ConvertValueUnit_throws_UnitsNetException_
6979
[Fact]
7080
public void UnitsNetBaseJsonConverter_ConvertValueUnit_throws_UnitsNetException_when_unit_is_in_unexpected_format()
7181
{
72-
var result = Assert.Throws<UnitsNetException>(() => _sut.Test_ConvertValueUnit("PowerUnit Watt", 10.2365D));
82+
var result = Assert.Throws<UnitsNetException>(() => _sut.Test_ConvertDecimalValueUnit("PowerUnit Watt", 10.2365m));
7383

7484
Assert.Equal("\"PowerUnit Watt\" is not a valid unit.", result.Message);
7585
Assert.True(result.Data.Contains("type"));
@@ -85,7 +95,7 @@ public void UnitsNetBaseJsonConverter_CreateLocalSerializer_works_as_expected()
8595
TypeNameHandling = TypeNameHandling.Arrays,
8696
Converters = new List<JsonConverter>()
8797
{
88-
98+
8999
new BinaryConverter(),
90100
_sut,
91101
new DataTableConverter()
@@ -109,21 +119,21 @@ public void UnitsNetBaseJsonConverter_ReadValueUnit_work_as_expected()
109119
var token = new JObject();
110120

111121
token.Add("Unit", "PowerUnit.Watt");
112-
token.Add("Value", 10.2365D);
122+
token.Add("Value", "10.2365");
113123

114-
var result = _sut.Test_ReadValueUnit(token);
124+
var result = _sut.Test_ReadDecimalValueUnit(token);
115125

116126
Assert.NotNull(result);
117127
Assert.Equal("PowerUnit.Watt", result?.Unit);
118-
Assert.Equal(10.2365D, result?.Value);
128+
Assert.Equal(10.2365m, result?.Value);
119129
}
120130

121131
[Fact]
122132
public void UnitsNetBaseJsonConverter_ReadValueUnit_works_with_empty_token()
123133
{
124134
var token = new JObject();
125135

126-
var result = _sut.Test_ReadValueUnit(token);
136+
var result = _sut.Test_ReadDoubleValueUnit(token);
127137

128138
Assert.Null(result);
129139
}
@@ -142,10 +152,10 @@ public void UnitsNetBaseJsonConverter_ReadValueUnit_returns_null_when_unit_or_va
142152

143153
if (withValue)
144154
{
145-
token.Add("Value", 10.2365D);
155+
token.Add("Value", 10.2365m);
146156
}
147157

148-
var result = _sut.Test_ReadValueUnit(token);
158+
var result = _sut.Test_ReadDecimalValueUnit(token);
149159

150160
Assert.Null(result);
151161
}
@@ -161,13 +171,13 @@ public void UnitsNetBaseJsonConverter_ReadValueUnit_works_case_insensitive(strin
161171
var token = new JObject();
162172

163173
token.Add(unitPropertyName, "PowerUnit.Watt");
164-
token.Add(valuePropertyName, 10.2365D);
174+
token.Add(valuePropertyName, 10.2365m.ToString(CultureInfo.InvariantCulture));
165175

166-
var result = _sut.Test_ReadValueUnit(token);
176+
var result = _sut.Test_ReadDecimalValueUnit(token);
167177

168178
Assert.NotNull(result);
169179
Assert.Equal("PowerUnit.Watt", result?.Unit);
170-
Assert.Equal(10.2365D, result?.Value);
180+
Assert.Equal(10.2365m, result?.Value);
171181
}
172182

173183
/// <summary>
@@ -180,30 +190,58 @@ private class TestConverter : UnitsNetBaseJsonConverter<string>
180190
public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) => throw new NotImplementedException();
181191
public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
182192

183-
public (string Unit, double Value) Test_ConvertIQuantity(IQuantity value)
193+
public (string Unit, double Value) Test_ConvertDoubleIQuantity(IQuantity value)
184194
{
185195
var result = ConvertIQuantity(value);
196+
if (result is DoubleValueUnit doubleResult)
197+
{
198+
return (result.Unit, doubleResult.Value);
199+
}
186200

187-
return (result.Unit, result.Value);
201+
throw new ArgumentException("The quantity does not have a double value", nameof(value));
188202
}
189203

190-
public IQuantity Test_ConvertValueUnit(string unit, double value) => Test_ConvertValueUnit(new ValueUnit() {Unit = unit, Value = value});
204+
public (string Unit, decimal Value) Test_ConvertDecimalIQuantity(IQuantity value)
205+
{
206+
var result = ConvertIQuantity(value);
207+
if (result is DecimalValueUnit decimalResult)
208+
{
209+
return (result.Unit, decimal.Parse(decimalResult.Value));
210+
}
211+
212+
throw new ArgumentException("The quantity does not have a decimal value", nameof(value));
213+
}
214+
215+
public IQuantity Test_ConvertDoubleValueUnit(string unit, double value) => Test_ConvertValueUnit(new DoubleValueUnit {Unit = unit, Value = value});
216+
public IQuantity Test_ConvertDecimalValueUnit(string unit, decimal value) => Test_ConvertValueUnit(new DecimalValueUnit {Unit = unit, Value = value.ToString(CultureInfo.InvariantCulture)});
191217
public IQuantity Test_ConvertValueUnit() => Test_ConvertValueUnit(null);
192218
private IQuantity Test_ConvertValueUnit(ValueUnit valueUnit) => ConvertValueUnit(valueUnit);
193219

194220
public JsonSerializer Test_CreateLocalSerializer(JsonSerializer serializer) => CreateLocalSerializer(serializer, this);
195221

196-
public (string Unit, double Value)? Test_ReadValueUnit(JToken jsonToken)
222+
public (string Unit, double Value)? Test_ReadDoubleValueUnit(JToken jsonToken)
197223
{
198224
var result = ReadValueUnit(jsonToken);
225+
if (result is DoubleValueUnit doubleResult)
226+
{
227+
return (result.Unit, doubleResult.Value);
228+
}
199229

200-
if (result == null)
230+
return null;
231+
}
232+
233+
public (string Unit, decimal Value)? Test_ReadDecimalValueUnit(JToken jsonToken)
234+
{
235+
var result = ReadValueUnit(jsonToken);
236+
237+
if (result is DecimalValueUnit decimalResult)
201238
{
202-
return null;
239+
return (result.Unit, decimal.Parse(decimalResult.Value));
203240
}
204241

205-
return (result.Unit, result.Value);
242+
return null;
206243
}
244+
207245
}
208246
}
209247
}

UnitsNet.Serialization.JsonNet.Tests/UnitsNetIQuantityJsonConverterTest.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,33 @@ public void UnitsNetIQuantityJsonConverter_WriteJson_works_with_NULL_value()
5656
}
5757

5858
[Fact]
59-
public void UnitsNetIQuantityJsonConverter_WriteJson_works_as_expected()
59+
public void UnitsNetIQuantityJsonConverter_WriteJson_works_with_double_quantity()
60+
{
61+
var result = new StringBuilder();
62+
63+
using (var stringWriter = new StringWriter(result))
64+
using(var writer = new JsonTextWriter(stringWriter))
65+
{
66+
_sut.WriteJson(writer, Length.FromMeters(10.2365D), JsonSerializer.CreateDefault());
67+
}
68+
69+
Assert.Equal("{\"Unit\":\"LengthUnit.Meter\",\"Value\":10.2365}", result.ToString());
70+
}
71+
72+
[Theory]
73+
[InlineData(10.2365, "10.2365")]
74+
[InlineData(10, "10")]
75+
public void UnitsNetIQuantityJsonConverter_WriteJson_works_with_decimal_quantity(decimal value, string expectedSerializedValue)
6076
{
6177
var result = new StringBuilder();
6278

6379
using (var stringWriter = new StringWriter(result))
6480
using(var writer = new JsonTextWriter(stringWriter))
6581
{
66-
_sut.WriteJson(writer, Power.FromWatts(10.2365D), JsonSerializer.CreateDefault());
82+
_sut.WriteJson(writer, Power.FromWatts(value), JsonSerializer.CreateDefault());
6783
}
6884

69-
Assert.Equal("{\"Unit\":\"PowerUnit.Watt\",\"Value\":10.2365}", result.ToString());
85+
Assert.Equal($"{{\"Unit\":\"PowerUnit.Watt\",\"Value\":\"{expectedSerializedValue}\"}}", result.ToString());
7086
}
7187

7288
[Fact]

UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonDeserializationTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@ public void Information_CanDeserializeVeryLargeValues()
1919
Assert.Equal(original, deserialized);
2020
}
2121

22+
[Fact]
23+
public void Information_CanDeserializeMaxValue()
24+
{
25+
var original = Information.MaxValue;
26+
var json = SerializeObject(original);
27+
var deserialized = DeserializeObject<Information>(json);
28+
29+
Assert.Equal(original, deserialized);
30+
}
31+
32+
[Fact]
33+
public void Information_CanDeserializeMinValue()
34+
{
35+
var original = Information.MinValue;
36+
var json = SerializeObject(original);
37+
var deserialized = DeserializeObject<Information>(json);
38+
39+
Assert.Equal(original, deserialized);
40+
}
41+
42+
[Fact]
43+
public void Length_CanDeserializeMaxValue()
44+
{
45+
var original = Length.MaxValue;
46+
var json = SerializeObject(original);
47+
var deserialized = DeserializeObject<Length>(json);
48+
49+
Assert.Equal(original, deserialized);
50+
}
51+
52+
[Fact]
53+
public void Length_CanDeserializeMinValue()
54+
{
55+
var original = Length.MinValue;
56+
var json = SerializeObject(original);
57+
var deserialized = DeserializeObject<Length>(json);
58+
59+
Assert.Equal(original, deserialized);
60+
}
61+
2262
[Fact]
2363
public void Mass_ExpectJsonCorrectlyDeserialized()
2464
{

UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonSerializationTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed class UnitsNetJsonSerializationTests : UnitsNetJsonBaseTest
1212
public void Information_CanSerializeVeryLargeValues()
1313
{
1414
Information i = Information.FromExabytes(1E+9);
15-
var expectedJson = "{\n \"Unit\": \"InformationUnit.Exabyte\",\n \"Value\": 1000000000.0\n}";
15+
var expectedJson = "{\n \"Unit\": \"InformationUnit.Exabyte\",\n \"Value\": \"1000000000\"\n}";
1616

1717
string json = SerializeObject(i);
1818

@@ -34,7 +34,7 @@ public void Mass_ExpectConstructedValueAndUnit()
3434
public void Information_ExpectConstructedValueAndUnit()
3535
{
3636
Information quantity = Information.FromKilobytes(54);
37-
var expectedJson = "{\n \"Unit\": \"InformationUnit.Kilobyte\",\n \"Value\": 54.0\n}";
37+
var expectedJson = "{\n \"Unit\": \"InformationUnit.Kilobyte\",\n \"Value\": \"54\"\n}";
3838

3939
string json = SerializeObject(quantity);
4040

@@ -148,7 +148,7 @@ public void MultiDimArrayValue_ExpectJsonArray()
148148
"]";
149149

150150
string json = SerializeObject(testObj);
151-
151+
152152
Assert.Equal(expectedJson, json);
153153
}
154154

0 commit comments

Comments
 (0)