diff --git a/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs b/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs index e5ea1c72c9..d759566c0a 100644 --- a/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs +++ b/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs @@ -69,8 +69,15 @@ namespace UnitsNet /// {_quantity.XmlDocRemarks} /// "); + Writer.W(@$" + public partial struct {_quantity.Name} : IQuantity<{_unitEnumName}>, "); + if (_quantity.BaseType == "decimal") + { + Writer.W("IDecimalQuantity, "); + } + + Writer.WL($"IEquatable<{_quantity.Name}>, IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable"); Writer.WL($@" - public partial struct {_quantity.Name} : IQuantity<{_unitEnumName}>, IEquatable<{_quantity.Name}>, IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable {{ /// /// The numeric value this quantity was constructed with. @@ -269,6 +276,11 @@ private void GenerateProperties() Writer.WL(@" double IQuantity.Value => (double) _value; "); + if (_quantity.BaseType == "decimal") + Writer.WL(@" + /// + decimal IDecimalQuantity.Value => _value; +"); Writer.WL($@" Enum IQuantity.Unit => Unit; diff --git a/CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs b/CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs index 6256d3310d..352f895108 100644 --- a/CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs +++ b/CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs @@ -110,7 +110,10 @@ public void Ctor_WithUndefinedUnit_ThrowsArgumentException() public void DefaultCtor_ReturnsQuantityWithZeroValueAndBaseUnit() {{ var quantity = new {_quantity.Name}(); - Assert.Equal(0, quantity.Value); + Assert.Equal(0, quantity.Value);"); + if (_quantity.BaseType == "decimal") Writer.WL($@" + Assert.Equal(0m, ((IDecimalQuantity)quantity).Value);"); + Writer.WL($@" Assert.Equal({_baseUnitFullName}, quantity.Unit); }} diff --git a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetBaseJsonConverterTest.cs b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetBaseJsonConverterTest.cs index eeaaaf0c5e..5ceaa671af 100644 --- a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetBaseJsonConverterTest.cs +++ b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetBaseJsonConverterTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -13,7 +14,7 @@ namespace UnitsNet.Serialization.JsonNet.Tests { public sealed class UnitsNetBaseJsonConverterTest { - private TestConverter _sut; + private readonly TestConverter _sut; public UnitsNetBaseJsonConverterTest() { @@ -21,18 +22,27 @@ public UnitsNetBaseJsonConverterTest() } [Fact] - public void UnitsNetBaseJsonConverter_ConvertIQuantity_works_as_expected() + public void UnitsNetBaseJsonConverter_ConvertIQuantity_works_with_double_type() { - var result = _sut.Test_ConvertIQuantity(Power.FromWatts(10.2365D)); + var result = _sut.Test_ConvertDoubleIQuantity(Length.FromMeters(10.2365)); + + Assert.Equal("LengthUnit.Meter", result.Unit); + Assert.Equal(10.2365, result.Value); + } + + [Fact] + public void UnitsNetBaseJsonConverter_ConvertIQuantity_works_with_decimal_type() + { + var result = _sut.Test_ConvertDecimalIQuantity(Power.FromWatts(10.2365m)); Assert.Equal("PowerUnit.Watt", result.Unit); - Assert.Equal(10.2365D, result.Value); + Assert.Equal(10.2365m, result.Value); } [Fact] public void UnitsNetBaseJsonConverter_ConvertIQuantity_throws_ArgumentNullException_when_quantity_is_NULL() { - var result = Assert.Throws(() => _sut.Test_ConvertIQuantity(null)); + var result = Assert.Throws(() => _sut.Test_ConvertDoubleIQuantity(null)); Assert.Equal("Value cannot be null.\r\nParameter name: quantity", result.Message); } @@ -40,11 +50,11 @@ public void UnitsNetBaseJsonConverter_ConvertIQuantity_throws_ArgumentNullExcept [Fact] public void UnitsNetBaseJsonConverter_ConvertValueUnit_works_as_expected() { - var result = _sut.Test_ConvertValueUnit("PowerUnit.Watt", 10.2365D); + var result = _sut.Test_ConvertDecimalValueUnit("PowerUnit.Watt", 10.2365m); Assert.NotNull(result); Assert.IsType(result); - Assert.True(Power.FromWatts(10.2365D).Equals((Power)result, 1E-5, ComparisonType.Absolute)); + Assert.True(Power.FromWatts(10.2365m).Equals((Power)result, 1E-5, ComparisonType.Absolute)); } @@ -59,7 +69,7 @@ public void UnitsNetBaseJsonConverter_ConvertValueUnit_works_with_NULL_value() [Fact] public void UnitsNetBaseJsonConverter_ConvertValueUnit_throws_UnitsNetException_when_unit_does_not_exist() { - var result = Assert.Throws(() => _sut.Test_ConvertValueUnit("SomeImaginaryUnit.Watt", 10.2365D)); + var result = Assert.Throws(() => _sut.Test_ConvertDoubleValueUnit("SomeImaginaryUnit.Watt", 10.2365D)); Assert.Equal("Unable to find enum type.", result.Message); Assert.True(result.Data.Contains("type")); @@ -69,7 +79,7 @@ public void UnitsNetBaseJsonConverter_ConvertValueUnit_throws_UnitsNetException_ [Fact] public void UnitsNetBaseJsonConverter_ConvertValueUnit_throws_UnitsNetException_when_unit_is_in_unexpected_format() { - var result = Assert.Throws(() => _sut.Test_ConvertValueUnit("PowerUnit Watt", 10.2365D)); + var result = Assert.Throws(() => _sut.Test_ConvertDecimalValueUnit("PowerUnit Watt", 10.2365m)); Assert.Equal("\"PowerUnit Watt\" is not a valid unit.", result.Message); Assert.True(result.Data.Contains("type")); @@ -85,7 +95,7 @@ public void UnitsNetBaseJsonConverter_CreateLocalSerializer_works_as_expected() TypeNameHandling = TypeNameHandling.Arrays, Converters = new List() { - + new BinaryConverter(), _sut, new DataTableConverter() @@ -104,26 +114,56 @@ public void UnitsNetBaseJsonConverter_CreateLocalSerializer_works_as_expected() } [Fact] - public void UnitsNetBaseJsonConverter_ReadValueUnit_work_as_expected() + public void UnitsNetBaseJsonConverter_ReadValueUnit_works_with_double_quantity() { - var token = new JObject(); + var token = new JObject {{"Unit", "LengthUnit.Meter"}, {"Value", 10.2365}}; - token.Add("Unit", "PowerUnit.Watt"); - token.Add("Value", 10.2365D); + var result = _sut.Test_ReadDoubleValueUnit(token); + + Assert.NotNull(result); + Assert.Equal("LengthUnit.Meter", result?.Unit); + Assert.Equal(10.2365, result?.Value); + } + + [Fact] + public void UnitsNetBaseJsonConverter_ReadValueUnit_works_with_decimal_quantity() + { + var token = new JObject {{"Unit", "PowerUnit.Watt"}, {"Value", 10.2365m}, {"ValueString", "10.2365"}, {"ValueType", "decimal"}}; - var result = _sut.Test_ReadValueUnit(token); + var result = _sut.Test_ReadDecimalValueUnit(token); Assert.NotNull(result); Assert.Equal("PowerUnit.Watt", result?.Unit); - Assert.Equal(10.2365D, result?.Value); + Assert.Equal(10.2365m, result?.Value); + } + + [Fact] + public void UnitsNetBaseJsonConverter_ReadValueUnit_returns_null_when_value_is_a_string() + { + var token = new JObject {{"Unit", "PowerUnit.Watt"}, {"Value", "10.2365"}}; + + var result = _sut.Test_ReadDecimalValueUnit(token); + + Assert.Null(result); + } + + [Fact] + public void UnitsNetBaseJsonConverter_ReadValueUnit_returns_null_when_value_type_is_not_a_string() + { + var token = new JObject {{"Unit", "PowerUnit.Watt"}, {"Value", 10.2365}, {"ValueType", 123}}; + + var result = _sut.Test_ReadDecimalValueUnit(token); + + Assert.Null(result); } + [Fact] - public void UnitsNetBaseJsonConverter_ReadValueUnit_works_with_empty_token() + public void UnitsNetBaseJsonConverter_ReadDoubleValueUnit_works_with_empty_token() { var token = new JObject(); - var result = _sut.Test_ReadValueUnit(token); + var result = _sut.Test_ReadDoubleValueUnit(token); Assert.Null(result); } @@ -142,32 +182,40 @@ public void UnitsNetBaseJsonConverter_ReadValueUnit_returns_null_when_unit_or_va if (withValue) { - token.Add("Value", 10.2365D); + token.Add("Value", 10.2365m); } - var result = _sut.Test_ReadValueUnit(token); + var result = _sut.Test_ReadDecimalValueUnit(token); Assert.Null(result); } [Theory] - [InlineData("Unit", "Value")] - [InlineData("unit", "Value")] - [InlineData("Unit", "value")] - [InlineData("unit", "value")] - [InlineData("unIT", "vAlUe")] - public void UnitsNetBaseJsonConverter_ReadValueUnit_works_case_insensitive(string unitPropertyName, string valuePropertyName) + [InlineData("Unit", "Value", "ValueString", "ValueType")] + [InlineData("unit", "Value", "ValueString", "ValueType")] + [InlineData("Unit", "value", "valueString", "valueType")] + [InlineData("unit", "value", "valueString", "valueType")] + [InlineData("unIT", "vAlUe", "vAlUeString", "vAlUeType")] + public void UnitsNetBaseJsonConverter_ReadValueUnit_works_case_insensitive( + string unitPropertyName, + string valuePropertyName, + string valueStringPropertyName, + string valueTypePropertyName) { - var token = new JObject(); + var token = new JObject + { + {unitPropertyName, "PowerUnit.Watt"}, + {valuePropertyName, 10.2365m}, + {valueStringPropertyName, 10.2365m.ToString(CultureInfo.InvariantCulture)}, + {valueTypePropertyName, "decimal"} + }; - token.Add(unitPropertyName, "PowerUnit.Watt"); - token.Add(valuePropertyName, 10.2365D); - var result = _sut.Test_ReadValueUnit(token); + var result = _sut.Test_ReadDecimalValueUnit(token); Assert.NotNull(result); Assert.Equal("PowerUnit.Watt", result?.Unit); - Assert.Equal(10.2365D, result?.Value); + Assert.Equal(10.2365m, result?.Value); } /// @@ -180,23 +228,38 @@ private class TestConverter : UnitsNetBaseJsonConverter public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) => throw new NotImplementedException(); public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); - public (string Unit, double Value) Test_ConvertIQuantity(IQuantity value) + public (string Unit, double Value) Test_ConvertDoubleIQuantity(IQuantity value) { var result = ConvertIQuantity(value); - return (result.Unit, result.Value); } - public IQuantity Test_ConvertValueUnit(string unit, double value) => Test_ConvertValueUnit(new ValueUnit() {Unit = unit, Value = value}); + public (string Unit, decimal Value) Test_ConvertDecimalIQuantity(IQuantity value) + { + var result = ConvertIQuantity(value); + if (result is ExtendedValueUnit {ValueType: "decimal"} decimalResult) + { + return (result.Unit, decimal.Parse(decimalResult.ValueString)); + } + + throw new ArgumentException("The quantity does not have a decimal value", nameof(value)); + } + + public IQuantity Test_ConvertDoubleValueUnit(string unit, double value) => Test_ConvertValueUnit(new ValueUnit {Unit = unit, Value = value}); + + public IQuantity Test_ConvertDecimalValueUnit(string unit, decimal value) => Test_ConvertValueUnit(new ExtendedValueUnit + { + Unit = unit, Value = (double) value, ValueString = value.ToString(CultureInfo.InvariantCulture), ValueType = "decimal" + }); + public IQuantity Test_ConvertValueUnit() => Test_ConvertValueUnit(null); private IQuantity Test_ConvertValueUnit(ValueUnit valueUnit) => ConvertValueUnit(valueUnit); public JsonSerializer Test_CreateLocalSerializer(JsonSerializer serializer) => CreateLocalSerializer(serializer, this); - public (string Unit, double Value)? Test_ReadValueUnit(JToken jsonToken) + public (string Unit, double Value)? Test_ReadDoubleValueUnit(JToken jsonToken) { var result = ReadValueUnit(jsonToken); - if (result == null) { return null; @@ -204,6 +267,19 @@ private class TestConverter : UnitsNetBaseJsonConverter return (result.Unit, result.Value); } + + public (string Unit, decimal Value)? Test_ReadDecimalValueUnit(JToken jsonToken) + { + var result = ReadValueUnit(jsonToken); + + if (result is ExtendedValueUnit {ValueType: "decimal"} decimalResult) + { + return (result.Unit, decimal.Parse(decimalResult.ValueString)); + } + + return null; + } + } } } diff --git a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetIQuantityJsonConverterTest.cs b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetIQuantityJsonConverterTest.cs index 429466036c..09b89e6c7f 100644 --- a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetIQuantityJsonConverterTest.cs +++ b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetIQuantityJsonConverterTest.cs @@ -56,17 +56,34 @@ public void UnitsNetIQuantityJsonConverter_WriteJson_works_with_NULL_value() } [Fact] - public void UnitsNetIQuantityJsonConverter_WriteJson_works_as_expected() + public void UnitsNetIQuantityJsonConverter_WriteJson_works_with_double_quantity() + { + var result = new StringBuilder(); + + using (var stringWriter = new StringWriter(result)) + using(var writer = new JsonTextWriter(stringWriter)) + { + _sut.WriteJson(writer, Length.FromMeters(10.2365D), JsonSerializer.CreateDefault()); + } + + Assert.Equal("{\"Unit\":\"LengthUnit.Meter\",\"Value\":10.2365}", result.ToString()); + } + + [Theory] + [InlineData(10.2365, "10.2365", "10.2365")] + [InlineData(10, "10.0", "10")] // Json.NET adds .0 + public void UnitsNetIQuantityJsonConverter_WriteJson_works_with_decimal_quantity(decimal value, string expectedValue, string expectedValueString) { var result = new StringBuilder(); using (var stringWriter = new StringWriter(result)) using(var writer = new JsonTextWriter(stringWriter)) { - _sut.WriteJson(writer, Power.FromWatts(10.2365D), JsonSerializer.CreateDefault()); + _sut.WriteJson(writer, Power.FromWatts(value), JsonSerializer.CreateDefault()); } - Assert.Equal("{\"Unit\":\"PowerUnit.Watt\",\"Value\":10.2365}", result.ToString()); + Assert.Equal($"{{\"Unit\":\"PowerUnit.Watt\",\"Value\":{expectedValue},\"ValueString\":\"{expectedValueString}\",\"ValueType\":\"decimal\"}}", + result.ToString()); } [Fact] diff --git a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonDeserializationTests.cs b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonDeserializationTests.cs index 610f45c976..f789666e18 100644 --- a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonDeserializationTests.cs +++ b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonDeserializationTests.cs @@ -19,6 +19,46 @@ public void Information_CanDeserializeVeryLargeValues() Assert.Equal(original, deserialized); } + [Fact] + public void Information_CanDeserializeMaxValue() + { + var original = Information.MaxValue; + var json = SerializeObject(original); + var deserialized = DeserializeObject(json); + + Assert.Equal(original, deserialized); + } + + [Fact] + public void Information_CanDeserializeMinValue() + { + var original = Information.MinValue; + var json = SerializeObject(original); + var deserialized = DeserializeObject(json); + + Assert.Equal(original, deserialized); + } + + [Fact] + public void Length_CanDeserializeMaxValue() + { + var original = Length.MaxValue; + var json = SerializeObject(original); + var deserialized = DeserializeObject(json); + + Assert.Equal(original, deserialized); + } + + [Fact] + public void Length_CanDeserializeMinValue() + { + var original = Length.MinValue; + var json = SerializeObject(original); + var deserialized = DeserializeObject(json); + + Assert.Equal(original, deserialized); + } + [Fact] public void Mass_ExpectJsonCorrectlyDeserialized() { diff --git a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonSerializationTests.cs b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonSerializationTests.cs index 537b7d3c92..ecad2cf236 100644 --- a/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonSerializationTests.cs +++ b/UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonSerializationTests.cs @@ -12,7 +12,7 @@ public sealed class UnitsNetJsonSerializationTests : UnitsNetJsonBaseTest public void Information_CanSerializeVeryLargeValues() { Information i = Information.FromExabytes(1E+9); - var expectedJson = "{\n \"Unit\": \"InformationUnit.Exabyte\",\n \"Value\": 1000000000.0\n}"; + var expectedJson = "{\n \"Unit\": \"InformationUnit.Exabyte\",\n \"Value\": 1000000000.0,\n \"ValueString\": \"1000000000\",\n \"ValueType\": \"decimal\"\n}"; string json = SerializeObject(i); @@ -34,7 +34,7 @@ public void Mass_ExpectConstructedValueAndUnit() public void Information_ExpectConstructedValueAndUnit() { Information quantity = Information.FromKilobytes(54); - var expectedJson = "{\n \"Unit\": \"InformationUnit.Kilobyte\",\n \"Value\": 54.0\n}"; + var expectedJson = "{\n \"Unit\": \"InformationUnit.Kilobyte\",\n \"Value\": 54.0,\n \"ValueString\": \"54\",\n \"ValueType\": \"decimal\"\n}"; string json = SerializeObject(quantity); @@ -148,7 +148,7 @@ public void MultiDimArrayValue_ExpectJsonArray() "]"; string json = SerializeObject(testObj); - + Assert.Equal(expectedJson, json); } diff --git a/UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs b/UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs index d5d1905ae3..9ca02e4275 100644 --- a/UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs +++ b/UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs @@ -2,6 +2,7 @@ // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. using System; +using System.Globalization; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; @@ -16,7 +17,7 @@ namespace UnitsNet.Serialization.JsonNet /// The type being converted. Should either be or public abstract class UnitsNetBaseJsonConverter : JsonConverter { - /// + /// /// Reads the "Unit" and "Value" properties from a JSON string /// /// The JSON data to read from @@ -32,16 +33,35 @@ protected ValueUnit ReadValueUnit(JToken jsonToken) var unit = jsonObject.GetValue(nameof(ValueUnit.Unit), StringComparison.OrdinalIgnoreCase); var value = jsonObject.GetValue(nameof(ValueUnit.Value), StringComparison.OrdinalIgnoreCase); + var valueType = jsonObject.GetValue(nameof(ExtendedValueUnit.ValueType), StringComparison.OrdinalIgnoreCase); + var valueString = jsonObject.GetValue(nameof(ExtendedValueUnit.ValueString), StringComparison.OrdinalIgnoreCase); if (unit == null || value == null) { return null; } - return new ValueUnit() + if (valueType == null) + { + if (value.Type != JTokenType.Float && value.Type != JTokenType.Integer) + { + return null; + } + + return new ValueUnit {Unit = unit.Value(), Value = value.Value()}; + } + + if (valueType.Type != JTokenType.String) + { + return null; + } + + return new ExtendedValueUnit { Unit = unit.Value(), - Value = value.Value() + Value = value.Value(), + ValueType = valueType.Value(), + ValueString = valueString?.Value() }; } @@ -53,17 +73,28 @@ protected ValueUnit ReadValueUnit(JToken jsonToken) /// An IQuantity protected IQuantity ConvertValueUnit(ValueUnit valueUnit) { - if (valueUnit == null || string.IsNullOrWhiteSpace(valueUnit.Unit)) + if (string.IsNullOrWhiteSpace(valueUnit?.Unit)) { return null; } - var unitParts = valueUnit.Unit.Split('.'); + var unit = GetUnit(valueUnit.Unit); + + return valueUnit switch + { + ExtendedValueUnit {ValueType: "decimal"} extendedValueUnit => Quantity.From(decimal.Parse(extendedValueUnit.ValueString), unit), + _ => Quantity.From(valueUnit.Value, unit) + }; + } + + private static Enum GetUnit(string unit) + { + var unitParts = unit.Split('.'); if (unitParts.Length != 2) { - var ex = new UnitsNetException($"\"{valueUnit.Unit}\" is not a valid unit."); - ex.Data["type"] = valueUnit.Unit; + var ex = new UnitsNetException($"\"{unit}\" is not a valid unit."); + ex.Data["type"] = unit; throw ex; } @@ -83,27 +114,31 @@ protected IQuantity ConvertValueUnit(ValueUnit valueUnit) throw ex; } - var value = valueUnit.Value; - var unitValue = (Enum)Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram - - return Quantity.From(value, unitValue); + var unitValue = (Enum) Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram + return unitValue; } /// /// Convert an to a /// /// The quantity to convert - /// + /// A serializable object. protected ValueUnit ConvertIQuantity(IQuantity quantity) { quantity = quantity ?? throw new ArgumentNullException(nameof(quantity)); - return new ValueUnit + if (quantity is IDecimalQuantity d) { - // See ValueUnit about precision loss for quantities using decimal type. - Value = quantity.Value, - Unit = $"{quantity.QuantityInfo.UnitType.Name}.{quantity.Unit}" - }; + return new ExtendedValueUnit + { + Unit = $"{quantity.QuantityInfo.UnitType.Name}.{quantity.Unit}", + Value = quantity.Value, + ValueString = d.Value.ToString(CultureInfo.InvariantCulture), + ValueType = "decimal" + }; + } + + return new ValueUnit {Value = quantity.Value, Unit = $"{quantity.QuantityInfo.UnitType.Name}.{quantity.Unit}"}; } /// @@ -156,28 +191,43 @@ protected JsonSerializer CreateLocalSerializer(JsonSerializer serializer, JsonCo /// /// A structure used to serialize/deserialize Units.NET unit instances. /// - /// - /// Quantities may use decimal, long or double as base value type and this might result - /// in a loss of precision when serializing/deserializing to decimal. - /// Decimal is the highest precision type available in .NET, but has a smaller - /// range than double. - /// - /// Json: Support decimal precision #503 - /// https://github.com/angularsen/UnitsNet/issues/503 - /// - protected sealed class ValueUnit + protected class ValueUnit { /// - /// The name of the unit + /// The unit of the value. /// /// MassUnit.Pound /// InformationUnit.Kilobyte + [JsonProperty(Order = 1)] public string Unit { get; [UsedImplicitly] set; } /// - /// The value of the unit + /// The value. /// + [JsonProperty(Order = 2)] public double Value { get; [UsedImplicitly] set; } } + + /// + /// A structure used to serialize/deserialize non-double Units.NET unit instances. + /// + /// + /// This type was added for lossless serialization of quantities with values. + /// The type distinguishes between 100 and 100.00 but Json.NET does not, therefore we serialize decimal values as string. + /// + protected sealed class ExtendedValueUnit : ValueUnit + { + /// + /// The value as a string. + /// + [JsonProperty(Order = 3)] + public string ValueString { get; [UsedImplicitly] set; } + + /// + /// The type of the value, e.g. "decimal". + /// + [JsonProperty(Order = 4)] + public string ValueType { get; [UsedImplicitly] set; } + } } } diff --git a/UnitsNet.Tests/GeneratedCode/TestsBase/BitRateTestsBase.g.cs b/UnitsNet.Tests/GeneratedCode/TestsBase/BitRateTestsBase.g.cs index 8c009d089d..b0333008f8 100644 --- a/UnitsNet.Tests/GeneratedCode/TestsBase/BitRateTestsBase.g.cs +++ b/UnitsNet.Tests/GeneratedCode/TestsBase/BitRateTestsBase.g.cs @@ -104,6 +104,7 @@ public void DefaultCtor_ReturnsQuantityWithZeroValueAndBaseUnit() { var quantity = new BitRate(); Assert.Equal(0, quantity.Value); + Assert.Equal(0m, ((IDecimalQuantity)quantity).Value); Assert.Equal(BitRateUnit.BitPerSecond, quantity.Unit); } diff --git a/UnitsNet.Tests/GeneratedCode/TestsBase/InformationTestsBase.g.cs b/UnitsNet.Tests/GeneratedCode/TestsBase/InformationTestsBase.g.cs index d3e12bb4d1..792be447fa 100644 --- a/UnitsNet.Tests/GeneratedCode/TestsBase/InformationTestsBase.g.cs +++ b/UnitsNet.Tests/GeneratedCode/TestsBase/InformationTestsBase.g.cs @@ -104,6 +104,7 @@ public void DefaultCtor_ReturnsQuantityWithZeroValueAndBaseUnit() { var quantity = new Information(); Assert.Equal(0, quantity.Value); + Assert.Equal(0m, ((IDecimalQuantity)quantity).Value); Assert.Equal(InformationUnit.Bit, quantity.Unit); } diff --git a/UnitsNet.Tests/GeneratedCode/TestsBase/PowerTestsBase.g.cs b/UnitsNet.Tests/GeneratedCode/TestsBase/PowerTestsBase.g.cs index 8533e228f0..edb1919faf 100644 --- a/UnitsNet.Tests/GeneratedCode/TestsBase/PowerTestsBase.g.cs +++ b/UnitsNet.Tests/GeneratedCode/TestsBase/PowerTestsBase.g.cs @@ -102,6 +102,7 @@ public void DefaultCtor_ReturnsQuantityWithZeroValueAndBaseUnit() { var quantity = new Power(); Assert.Equal(0, quantity.Value); + Assert.Equal(0m, ((IDecimalQuantity)quantity).Value); Assert.Equal(PowerUnit.Watt, quantity.Unit); } diff --git a/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs b/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs index 882a4e1608..e61dacd524 100644 --- a/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs +++ b/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs @@ -37,7 +37,7 @@ namespace UnitsNet /// /// https://en.wikipedia.org/wiki/Bit_rate /// - public partial struct BitRate : IQuantity, IEquatable, IComparable, IComparable, IConvertible, IFormattable + public partial struct BitRate : IQuantity, IDecimalQuantity, IEquatable, IComparable, IComparable, IConvertible, IFormattable { /// /// The numeric value this quantity was constructed with. @@ -171,6 +171,9 @@ public BitRate(decimal value, UnitSystem unitSystem) double IQuantity.Value => (double) _value; + /// + decimal IDecimalQuantity.Value => _value; + Enum IQuantity.Unit => Unit; /// diff --git a/UnitsNet/GeneratedCode/Quantities/Information.g.cs b/UnitsNet/GeneratedCode/Quantities/Information.g.cs index 9867605918..b94d6a26be 100644 --- a/UnitsNet/GeneratedCode/Quantities/Information.g.cs +++ b/UnitsNet/GeneratedCode/Quantities/Information.g.cs @@ -34,7 +34,7 @@ namespace UnitsNet /// /// In computing and telecommunications, a unit of information is the capacity of some standard data storage system or communication channel, used to measure the capacities of other systems and channels. In information theory, units of information are also used to measure the information contents or entropy of random variables. /// - public partial struct Information : IQuantity, IEquatable, IComparable, IComparable, IConvertible, IFormattable + public partial struct Information : IQuantity, IDecimalQuantity, IEquatable, IComparable, IComparable, IConvertible, IFormattable { /// /// The numeric value this quantity was constructed with. @@ -168,6 +168,9 @@ public Information(decimal value, UnitSystem unitSystem) double IQuantity.Value => (double) _value; + /// + decimal IDecimalQuantity.Value => _value; + Enum IQuantity.Unit => Unit; /// diff --git a/UnitsNet/GeneratedCode/Quantities/Power.g.cs b/UnitsNet/GeneratedCode/Quantities/Power.g.cs index 474357f033..4919698454 100644 --- a/UnitsNet/GeneratedCode/Quantities/Power.g.cs +++ b/UnitsNet/GeneratedCode/Quantities/Power.g.cs @@ -34,7 +34,7 @@ namespace UnitsNet /// /// In physics, power is the rate of doing work. It is equivalent to an amount of energy consumed per unit time. /// - public partial struct Power : IQuantity, IEquatable, IComparable, IComparable, IConvertible, IFormattable + public partial struct Power : IQuantity, IDecimalQuantity, IEquatable, IComparable, IComparable, IConvertible, IFormattable { /// /// The numeric value this quantity was constructed with. @@ -167,6 +167,9 @@ public Power(decimal value, UnitSystem unitSystem) double IQuantity.Value => (double) _value; + /// + decimal IDecimalQuantity.Value => _value; + Enum IQuantity.Unit => Unit; /// diff --git a/UnitsNet/IDecimalQuantity.cs b/UnitsNet/IDecimalQuantity.cs new file mode 100644 index 0000000000..41ccd7787f --- /dev/null +++ b/UnitsNet/IDecimalQuantity.cs @@ -0,0 +1,16 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +namespace UnitsNet +{ + /// + /// Represents a quantity with a decimal value. + /// + public interface IDecimalQuantity + { + /// + /// The decimal value this quantity was constructed with. + /// + decimal Value { get; } + } +}