From 783698b6d936a4d3155aae75c317130df730a0e6 Mon Sep 17 00:00:00 2001 From: Rafael Gomez Date: Sat, 20 Feb 2021 21:55:07 +0100 Subject: [PATCH] Started implementation --- .../Infrastructure/TestObject.cs | 11 + .../TestObjectWithIComparable.cs | 12 + .../TestObjectWithThreeIComparable.cs | 16 + ...TestObjectWithValueAndUnitAsIComparable.cs | 18 ++ .../TestObjectWithValueAsIComparable.cs | 32 ++ .../Infrastructure/UnitsNetJsonBaseTest.cs | 29 ++ .../TestObject.cs | 8 + ....Serialization.SystemTextJson.Tests.csproj | 27 ++ .../UnitsNetJsonBaseTest.cs | 26 ++ .../UnitsNetJsonDeserializationTests.cs | 288 ++++++++++++++++++ .../UnitsNetJsonSerializationTests.cs | 163 ++++++++++ ...itsNet.Serialization.SystemTextJson.csproj | 15 + .../UnitsNetJsonConverter.cs | 74 +++++ .../UnitsNetJsonConverterFactory.cs | 22 ++ UnitsNet.sln | 12 + 15 files changed, 753 insertions(+) create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObject.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithIComparable.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithThreeIComparable.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAndUnitAsIComparable.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAsIComparable.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/UnitsNetJsonBaseTest.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/TestObject.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/UnitsNet.Serialization.SystemTextJson.Tests.csproj create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonBaseTest.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonDeserializationTests.cs create mode 100644 UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonSerializationTests.cs create mode 100644 UnitsNet.Serialization.SystemTextJson/UnitsNet.Serialization.SystemTextJson.csproj create mode 100644 UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverter.cs create mode 100644 UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverterFactory.cs diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObject.cs b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObject.cs new file mode 100644 index 0000000000..ece3c2524a --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObject.cs @@ -0,0 +1,11 @@ +// 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.Serialization.SystemTextJson.Tests.Infrastructure +{ + public sealed class TestObject + { + public Frequency? NullableFrequency { get; set; } + public Frequency NonNullableFrequency { get; set; } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithIComparable.cs b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithIComparable.cs new file mode 100644 index 0000000000..49092bf358 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithIComparable.cs @@ -0,0 +1,12 @@ +// 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. + +using System; + +namespace UnitsNet.Serialization.SystemTextJson.Tests.Infrastructure +{ + public sealed class TestObjectWithIComparable + { + public IComparable Value { get; set; } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithThreeIComparable.cs b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithThreeIComparable.cs new file mode 100644 index 0000000000..d6747d911c --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithThreeIComparable.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. + +using System; + +namespace UnitsNet.Serialization.SystemTextJson.Tests.Infrastructure +{ + public sealed class TestObjectWithThreeIComparable + { + public IComparable Value1 { get; set; } + + public IComparable Value2 { get; set; } + + public IComparable Value3 { get; set; } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAndUnitAsIComparable.cs b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAndUnitAsIComparable.cs new file mode 100644 index 0000000000..2e9b8e708d --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAndUnitAsIComparable.cs @@ -0,0 +1,18 @@ +// 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. + +using System; + +namespace UnitsNet.Serialization.SystemTextJson.Tests.Infrastructure +{ + public sealed class TestObjectWithValueAndUnitAsIComparable : IComparable + { + public double Value { get; set; } + public string Unit { get; set; } + + public int CompareTo(object obj) + { + return ((IComparable)Value).CompareTo(obj); + } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAsIComparable.cs b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAsIComparable.cs new file mode 100644 index 0000000000..8c39e56e60 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/TestObjectWithValueAsIComparable.cs @@ -0,0 +1,32 @@ +// 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. + +using System; + +namespace UnitsNet.Serialization.SystemTextJson.Tests.Infrastructure +{ + public sealed class TestObjectWithValueAsIComparable : IComparable + { + public int Value { get; set; } + + public int CompareTo(object obj) + { + return ((IComparable)Value).CompareTo(obj); + } + + // Needed for verifying that the deserialized object is the same, should not affect the serialization code + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + return Value.Equals(((TestObjectWithValueAsIComparable)obj).Value); + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/UnitsNetJsonBaseTest.cs b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/UnitsNetJsonBaseTest.cs new file mode 100644 index 0000000000..3c69f03043 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/Infrastructure/UnitsNetJsonBaseTest.cs @@ -0,0 +1,29 @@ +// 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. + +using System.Text.Json; + +namespace UnitsNet.Serialization.SystemTextJson.Tests.Infrastructure +{ + public abstract class UnitsNetJsonBaseTest + { + private readonly JsonSerializerOptions _jsonSerializerSettings; + + protected UnitsNetJsonBaseTest() + { + _jsonSerializerSettings = new JsonSerializerOptions {WriteIndented = true}; + _jsonSerializerSettings.Converters.Add(new UnitsNetJsonConverterFactory()); + //_jsonSerializerSettings.Converters.Add(new UnitsNetIQuantityJsonConverter()); + //_jsonSerializerSettings.Converters.Add(new UnitsNetIComparableJsonConverter()); + } + + protected string SerializeObject(object obj) + { + return JsonSerializer.Serialize(obj, _jsonSerializerSettings).Replace("\r\n", "\n"); + } + + protected T DeserializeObject(string json) + { return JsonSerializer.Deserialize(json, _jsonSerializerSettings); + } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/TestObject.cs b/UnitsNet.Serialization.SystemTextJson.Tests/TestObject.cs new file mode 100644 index 0000000000..002e97090d --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/TestObject.cs @@ -0,0 +1,8 @@ +namespace UnitsNet.Serialization.SystemTextJson.Tests +{ + public sealed class TestObject + { + public Frequency? NullableFrequency { get; set; } + public Frequency NonNullableFrequency { get; set; } + } +} \ No newline at end of file diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNet.Serialization.SystemTextJson.Tests.csproj b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNet.Serialization.SystemTextJson.Tests.csproj new file mode 100644 index 0000000000..9c1ff17bbf --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNet.Serialization.SystemTextJson.Tests.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonBaseTest.cs b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonBaseTest.cs new file mode 100644 index 0000000000..a5c1c0d528 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonBaseTest.cs @@ -0,0 +1,26 @@ +using System.Text.Json; + +namespace UnitsNet.Serialization.SystemTextJson.Tests +{ + public abstract class UnitsNetJsonBaseTest + { + private readonly JsonSerializerOptions _jsonSerializerSettings; + + protected UnitsNetJsonBaseTest() + { + _jsonSerializerSettings = new JsonSerializerOptions {WriteIndented = true}; + _jsonSerializerSettings.Converters.Add(new UnitsNetJsonConverterFactory()); + //_jsonSerializerSettings.Converters.Add(new UnitsNetIQuantityJsonConverter()); + //_jsonSerializerSettings.Converters.Add(new UnitsNetIComparableJsonConverter()); + } + + protected string SerializeObject(object obj) + { + return JsonSerializer.Serialize(obj, _jsonSerializerSettings).Replace("\r\n", "\n"); + } + + protected T DeserializeObject(string json) + { return JsonSerializer.Deserialize(json, _jsonSerializerSettings); + } + } +} \ No newline at end of file diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonDeserializationTests.cs b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonDeserializationTests.cs new file mode 100644 index 0000000000..27ab39c11c --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonDeserializationTests.cs @@ -0,0 +1,288 @@ +// 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. + +using UnitsNet.Serialization.SystemTextJson.Tests.Infrastructure; +using Xunit; + +namespace UnitsNet.Serialization.SystemTextJson.Tests +{ + public sealed class UnitsNetJsonDeserializationTests : UnitsNetJsonBaseTest + { + [Fact] + public void Information_CanDeserializeVeryLargeValues() + { + var original = Information.FromExabytes(1E+9); + var json = SerializeObject(original); + var deserialized = DeserializeObject(json); + + 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() + { + var originalMass = Mass.FromKilograms(33.33); + var json = SerializeObject(originalMass); + + var deserializedMass = DeserializeObject(json); + + Assert.Equal(originalMass, deserializedMass); + } + + [Fact] + public void NonNullNullableValue_ExpectValueDeserializedCorrectly() + { + Mass? nullableMass = Mass.FromKilograms(10); + var json = SerializeObject(nullableMass); + + var deserializedNullableMass = DeserializeObject(json); + + Assert.Equal(nullableMass.Value, deserializedNullableMass); + } + + [Fact] + public void NonNullNullableValueNestedInObject_ExpectValueDeserializedCorrectly() + { + var testObj = new TestObject() + { + NullableFrequency = Frequency.FromHertz(10), + NonNullableFrequency = Frequency.FromHertz(10) + }; + var json = SerializeObject(testObj); + + var deserializedTestObj = DeserializeObject(json); + + Assert.Equal(testObj.NullableFrequency, deserializedTestObj.NullableFrequency); + } + + [Fact] + public void NullValue_ExpectNullReturned() + { + var json = SerializeObject(null); + var deserializedNullMass = DeserializeObject(json); + + Assert.Null(deserializedNullMass); + } + + [Fact] + public void NullValueNestedInObject_ExpectValueDeserializedToNullCorrectly() + { + var testObj = new TestObject() + { + NullableFrequency = null, + NonNullableFrequency = Frequency.FromHertz(10) + }; + var json = SerializeObject(testObj); + + var deserializedTestObj = DeserializeObject(json); + + Assert.Null(deserializedTestObj.NullableFrequency); + } + + [Fact] + public void UnitEnumChangedAfterSerialization_ExpectUnitCorrectlyDeserialized() + { + var originalMass = Mass.FromKilograms(33.33); + var json = SerializeObject(originalMass); + + // Someone manually changed the serialized JSON string to 1000 grams. + json = json.Replace("33.33", "1000"); + json = json.Replace("MassUnit.Kilogram", "MassUnit.Gram"); + + var deserializedMass = DeserializeObject(json); + + // The original value serialized was 33.33 kg, but someone edited the JSON to be 1000 g. We expect the JSON is + // still deserializable, and the correct value of 1000 g is obtained. + Assert.Equal(1000, deserializedMass.Grams); + } + + [Fact] + public void UnitInIComparable_ExpectUnitCorrectlyDeserialized() + { + var testObjWithIComparable = new TestObjectWithIComparable() + { + Value = Power.FromWatts(10) + }; + + var json = SerializeObject(testObjWithIComparable); + + var deserializedTestObject = DeserializeObject(json); + + Assert.IsType(deserializedTestObject.Value); + Assert.Equal(Power.FromWatts(10), (Power)deserializedTestObject.Value); + } + + [Fact] + public void DoubleInIComparable_ExpectUnitCorrectlyDeserialized() + { + var testObjWithIComparable = new TestObjectWithIComparable() + { + Value = 10.0 + }; + + var json = SerializeObject(testObjWithIComparable); + var deserializedTestObject = DeserializeObject(json); + + Assert.IsType(deserializedTestObject.Value); + Assert.Equal(10d, (double)deserializedTestObject.Value); + } + + [Fact] + public void ClassInIComparable_ExpectUnitCorrectlyDeserialized() + { + var testObjWithIComparable = new TestObjectWithIComparable() + { + Value = new TestObjectWithValueAsIComparable() { Value = 10 } + }; + + var json = SerializeObject(testObjWithIComparable); + var deserializedTestObject = DeserializeObject(json); + + Assert.IsType(deserializedTestObject.Value); + Assert.Equal(10d, ((TestObjectWithValueAsIComparable)deserializedTestObject.Value).Value); + } + + [Fact] + public void OtherObjectWithUnitAndValue_ExpectCorrectReturnValues() + { + var testObjWithValueAndUnit = new TestObjectWithValueAndUnitAsIComparable() + { + Value = 5, + Unit = "Test", + }; + + var json = SerializeObject(testObjWithValueAndUnit); + var deserializedTestObject = DeserializeObject(json); + + Assert.IsType(deserializedTestObject.Value); + Assert.Equal(5d, deserializedTestObject.Value); + Assert.Equal("Test", deserializedTestObject.Unit); + } + + [Fact] + public void ThreeObjectsInIComparableWithDifferentValues_ExpectAllCorrectlyDeserialized() + { + var testObjWithIComparable = new TestObjectWithThreeIComparable() + { + Value1 = 10.0, + Value2 = Power.FromWatts(19), + Value3 = new TestObjectWithValueAsIComparable() { Value = 10 }, + }; + var json = SerializeObject(testObjWithIComparable); + var deserializedTestObject = DeserializeObject(json); + + Assert.IsType(deserializedTestObject.Value1); + Assert.Equal(10d, deserializedTestObject.Value1); + + Assert.IsType(deserializedTestObject.Value2); + Assert.Equal(Power.FromWatts(19), deserializedTestObject.Value2); + + Assert.IsType(deserializedTestObject.Value3); + Assert.Equal(testObjWithIComparable.Value3, deserializedTestObject.Value3); + } + + [Fact] + public void ArrayOfUnits_ExpectCorrectlyDeserialized() + { + Frequency[] expected = { Frequency.FromHertz(10), Frequency.FromHertz(10) }; + + var json = "[\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10.0\n" + + " },\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10.0\n" + + " }\n" + + "]"; + + var result = DeserializeObject(json); + + Assert.Equal(expected, result); + } + + [Fact] + public void MultiDimArrayOfUnits_ExpectCorrectlyDeserialized() + { + Frequency[,] expected = { { Frequency.FromHertz(10), Frequency.FromHertz(10) }, { Frequency.FromHertz(10), Frequency.FromHertz(10) } }; + + var json = "[\n" + + " [\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10.0\n" + + " },\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10.0\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10.0\n" + + " },\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10.0\n" + + " }\n" + + " ]\n" + + "]"; + + var result = DeserializeObject(json); + + Assert.Equal(expected, result); + } + + [Fact] + public void EmptyArray_ExpectCorrectlyDeserialized() + { + var json = "[]"; + + var result = DeserializeObject(json); + + Assert.Empty(result); + } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonSerializationTests.cs b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonSerializationTests.cs new file mode 100644 index 0000000000..aab9ad16f7 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson.Tests/UnitsNetJsonSerializationTests.cs @@ -0,0 +1,163 @@ +using System; +using System.Text.Json; +using Xunit; + +namespace UnitsNet.Serialization.SystemTextJson.Tests +{ + public class UnitsNetJsonSerializationTests : UnitsNetJsonBaseTest + { + [Fact] + public void Information_CanSerializeVeryLargeValues() + { + Information i = Information.FromExabytes(1E+9); + var expectedJson = "{\n \"Unit\": \"InformationUnit.Exabyte\",\n \"Value\": 1000000000\n}"; + string json = SerializeObject(i); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void Mass_ExpectConstructedValueAndUnit() + { + Mass mass = Mass.FromPounds(200); + var expectedJson = "{\n \"Unit\": \"MassUnit.Pound\",\n \"Value\": 200\n}"; + + string json = SerializeObject(mass); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void Information_ExpectConstructedValueAndUnit() + { + Information quantity = Information.FromKilobytes(54); + var expectedJson = "{\n \"Unit\": \"InformationUnit.Kilobyte\",\n \"Value\": 54\n}"; + + string json = SerializeObject(quantity); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void NonNullNullableValue_ExpectJsonUnaffected() + { + Mass? nullableMass = Mass.FromKilograms(10); + var expectedJson = "{\n \"Unit\": \"MassUnit.Kilogram\",\n \"Value\": 10\n}"; + + string json = SerializeObject(nullableMass); + + // There shouldn't be any change in the JSON for the non-null nullable value. + Assert.Equal(expectedJson, json); + } + + [Fact] + public void NonNullNullableValueNestedInObject_ExpectJsonUnaffected() + { + var testObj = new TestObject() + { + NullableFrequency = Frequency.FromHertz(10), + NonNullableFrequency = Frequency.FromHertz(10) + }; + + // Ugly manually formatted JSON string is used because string literals with newlines are rendered differently + // on the build server (i.e. the build server uses '\r' instead of '\n') + string expectedJson = "{\n" + + " \"NullableFrequency\": {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " },\n" + + " \"NonNullableFrequency\": {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " }\n" + + "}"; + + string json = SerializeObject(testObj); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void NullValue_ExpectJsonContainsNullString() + { + string json = JsonSerializer.Serialize(null); + Assert.Equal("null", json); + } + + [Fact] + public void Ratio_ExpectDecimalFractionsUsedAsBaseValueAndUnit() + { + Ratio ratio = Ratio.FromPartsPerThousand(250); + var expectedJson = "{\n \"Unit\": \"RatioUnit.PartPerThousand\",\n \"Value\": 250\n}"; + + string json = SerializeObject(ratio); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void ArrayValue_ExpectJsonArray() + { + Frequency[] testObj = { Frequency.FromHertz(10), Frequency.FromHertz(10) }; + + string expectedJson = "[\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " },\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " }\n" + + "]"; + + string json = SerializeObject(testObj); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void MultiDimArrayValue_ExpectJsonArray() + { + Frequency[,] testObj = { { Frequency.FromHertz(10), Frequency.FromHertz(10) }, { Frequency.FromHertz(10), Frequency.FromHertz(10) } }; + + string expectedJson = "[\n" + + " [\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " },\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " },\n" + + " {\n" + + " \"Unit\": \"FrequencyUnit.Hertz\",\n" + + " \"Value\": 10\n" + + " }\n" + + " ]\n" + + "]"; + + string json = SerializeObject(testObj); + + Assert.Equal(expectedJson, json); + } + + [Fact] + public void EmptyArrayValue_ExpectJsonArray() + { + Frequency[] testObj = new Frequency[0]; + + string expectedJson = "[]"; + + string json = SerializeObject(testObj); + Assert.Equal(expectedJson, json); + } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson/UnitsNet.Serialization.SystemTextJson.csproj b/UnitsNet.Serialization.SystemTextJson/UnitsNet.Serialization.SystemTextJson.csproj new file mode 100644 index 0000000000..497c45d955 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson/UnitsNet.Serialization.SystemTextJson.csproj @@ -0,0 +1,15 @@ + + + + net5.0 + + + + + + + + + + + diff --git a/UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverter.cs b/UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverter.cs new file mode 100644 index 0000000000..32231b4606 --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverter.cs @@ -0,0 +1,74 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace UnitsNet.Serialization.SystemTextJson +{ + public class UnitsNetJsonConverter : JsonConverter + { + public override IQuantity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var valueUnit = JsonSerializer.Deserialize(ref reader,options); + return ParseValueUnit(valueUnit); + } + + public override void Write(Utf8JsonWriter writer, IQuantity value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, ToValueUnit(value), options); + } + + private static ValueUnit ToValueUnit(IQuantity value) + { + return new ValueUnit + { + // See ValueUnit about precision loss for quantities using decimal type. + Value = value.Value, + Unit = $"{value.QuantityInfo.UnitType.Name}.{value.Unit}" + }; + } + + private static IQuantity ParseValueUnit(ValueUnit vu) + { + // "MassUnit.Kilogram" => "MassUnit" and "Kilogram" + string[] splitted = vu.Unit.Split('.'); + string unitEnumTypeName = splitted[0]; + string unitEnumValue = splitted[1]; + + // "UnitsNet.Units.MassUnit,UnitsNet" + string unitEnumTypeAssemblyQualifiedName = "UnitsNet.Units." + unitEnumTypeName + ",UnitsNet"; + + // -- see http://stackoverflow.com/a/6465096/1256096 for details + Type unitEnumType = Type.GetType(unitEnumTypeAssemblyQualifiedName); + if (unitEnumType == null) + { + var ex = new UnitsNetException("Unable to find enum type."); + ex.Data["type"] = unitEnumTypeAssemblyQualifiedName; + throw ex; + } + + double value = vu.Value; + Enum unitValue = (Enum)Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram + + return Quantity.From(value, unitValue); + } + + /// + /// 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 + /// + private class ValueUnit + { + public string Unit { get; [UsedImplicitly] set; } + public double Value { get; [UsedImplicitly] set; } + } + } +} diff --git a/UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverterFactory.cs b/UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverterFactory.cs new file mode 100644 index 0000000000..360086f5ce --- /dev/null +++ b/UnitsNet.Serialization.SystemTextJson/UnitsNetJsonConverterFactory.cs @@ -0,0 +1,22 @@ +// 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. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace UnitsNet.Serialization.SystemTextJson +{ + public class UnitsNetJsonConverterFactory:JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + return typeof(IQuantity).IsAssignableFrom(typeToConvert); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return new UnitsNetJsonConverter(); + } + } +} diff --git a/UnitsNet.sln b/UnitsNet.sln index 5d2636654c..fab466d7e7 100644 --- a/UnitsNet.sln +++ b/UnitsNet.sln @@ -21,6 +21,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitsNet.NumberExtensions", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitsNet.NumberExtensions.Tests", "UnitsNet.NumberExtensions.Tests\UnitsNet.NumberExtensions.Tests.csproj", "{B4996AF5-9A8B-481A-9018-EC7F5B1605FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Serialization.SystemTextJson", "UnitsNet.Serialization.SystemTextJson\UnitsNet.Serialization.SystemTextJson.csproj", "{13E40334-808B-4AD2-BC8F-E93F36A1C6E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Serialization.SystemTextJson.Tests", "UnitsNet.Serialization.SystemTextJson.Tests\UnitsNet.Serialization.SystemTextJson.Tests.csproj", "{34B934D0-746A-495F-BEF2-CB20C2004889}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +67,14 @@ Global {B4996AF5-9A8B-481A-9018-EC7F5B1605FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B4996AF5-9A8B-481A-9018-EC7F5B1605FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4996AF5-9A8B-481A-9018-EC7F5B1605FF}.Release|Any CPU.Build.0 = Release|Any CPU + {13E40334-808B-4AD2-BC8F-E93F36A1C6E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13E40334-808B-4AD2-BC8F-E93F36A1C6E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13E40334-808B-4AD2-BC8F-E93F36A1C6E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13E40334-808B-4AD2-BC8F-E93F36A1C6E1}.Release|Any CPU.Build.0 = Release|Any CPU + {34B934D0-746A-495F-BEF2-CB20C2004889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34B934D0-746A-495F-BEF2-CB20C2004889}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34B934D0-746A-495F-BEF2-CB20C2004889}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34B934D0-746A-495F-BEF2-CB20C2004889}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE