diff --git a/UnitsNet.Serialization.JsonNet.Tests/CustomQuantities/HowMuchTests.cs b/UnitsNet.Serialization.JsonNet.Tests/CustomQuantities/HowMuchTests.cs new file mode 100644 index 0000000000..aa43df1015 --- /dev/null +++ b/UnitsNet.Serialization.JsonNet.Tests/CustomQuantities/HowMuchTests.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; +using UnitsNet.Serialization.JsonNet; +using Xunit; + +namespace UnitsNet.Tests.CustomQuantities +{ + public class HowMuchTests + { + [Fact] + public static void SerializeAndDeserializeCreatesSameObjectForIQuantity() + { + var jsonSerializerSettings = new JsonSerializerSettings { Formatting = Formatting.Indented }; + var quantityConverter = new UnitsNetIQuantityJsonConverter(); + quantityConverter.RegisterCustomType(typeof(HowMuch), typeof(HowMuchUnit)); + jsonSerializerSettings.Converters.Add(quantityConverter); + + var quantity = new HowMuch(12.34, HowMuchUnit.ATon); + + var serializedQuantity = JsonConvert.SerializeObject(quantity, jsonSerializerSettings); + + var deserializedQuantity = JsonConvert.DeserializeObject(serializedQuantity, jsonSerializerSettings); + Assert.Equal(quantity, deserializedQuantity); + } + } +} diff --git a/UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj b/UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj index 50c6292c2a..05eb397a50 100644 --- a/UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj +++ b/UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs b/UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs index 9ead71de7e..f73416c2ea 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.Collections.Concurrent; using System.Globalization; using System.Linq; using JetBrains.Annotations; @@ -17,6 +18,29 @@ namespace UnitsNet.Serialization.JsonNet /// The type being converted. Should either be or public abstract class UnitsNetBaseJsonConverter : JsonConverter { + private ConcurrentDictionary _registeredTypes = new(); + + /// + /// Register custom types so that the converter can instantiate these quantities. + /// Instead of calling , the will be used to instantiate the object. + /// It is therefore assumed that the constructor of is specified with new T(double value, typeof() unit). + /// Registering the same multiple times, it will overwrite the one registered. + /// + public void RegisterCustomType(Type quantity, Type unit) + { + if (!typeof(T).IsAssignableFrom(quantity)) + { + throw new ArgumentException($"The type {quantity} is not a {typeof(T)}"); + } + + if (!typeof(Enum).IsAssignableFrom(unit)) + { + throw new ArgumentException($"The type {unit} is not a {nameof(Enum)}"); + } + + _registeredTypes[unit.Name] = (quantity, unit); + } + /// /// Reads the "Unit" and "Value" properties from a JSON string /// @@ -79,6 +103,12 @@ protected IQuantity ConvertValueUnit(ValueUnit valueUnit) } var unit = GetUnit(valueUnit.Unit); + var registeredQuantity = GetRegisteredType(valueUnit.Unit).Quantity; + + if (registeredQuantity is not null) + { + return (IQuantity)Activator.CreateInstance(registeredQuantity, valueUnit.Value, unit); + } return valueUnit switch { @@ -87,7 +117,45 @@ protected IQuantity ConvertValueUnit(ValueUnit valueUnit) }; } - private static Enum GetUnit(string unit) + private (Type Quantity, Type Unit) GetRegisteredType(string unit) + { + (var unitEnumTypeName, var _) = SplitUnitString(unit); + if (_registeredTypes.TryGetValue(unitEnumTypeName, out var registeredType)) + { + return registeredType; + } + + return (null, null); + } + + private Enum GetUnit(string unit) + { + (var unitEnumTypeName, var unitEnumValue) = SplitUnitString(unit); + + // First try to find the name in the list of registered types. + var unitEnumType = GetRegisteredType(unit).Unit; + + if (unitEnumType is null) + { + // "UnitsNet.Units.MassUnit,UnitsNet" + var unitEnumTypeAssemblyQualifiedName = "UnitsNet.Units." + unitEnumTypeName + ",UnitsNet"; + + // -- see http://stackoverflow.com/a/6465096/1256096 for details + unitEnumType = Type.GetType(unitEnumTypeAssemblyQualifiedName); + + if (unitEnumType is null) + { + var ex = new UnitsNetException("Unable to find enum type."); + ex.Data["type"] = unitEnumTypeAssemblyQualifiedName; + throw ex; + } + } + + var unitValue = (Enum) Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram + return unitValue; + } + + private static (string EnumName, string EnumValue) SplitUnitString(string unit) { var unitParts = unit.Split('.'); @@ -99,23 +167,7 @@ private static Enum GetUnit(string unit) } // "MassUnit.Kilogram" => "MassUnit" and "Kilogram" - var unitEnumTypeName = unitParts[0]; - var unitEnumValue = unitParts[1]; - - // "UnitsNet.Units.MassUnit,UnitsNet" - var unitEnumTypeAssemblyQualifiedName = "UnitsNet.Units." + unitEnumTypeName + ",UnitsNet"; - - // -- see http://stackoverflow.com/a/6465096/1256096 for details - var unitEnumType = Type.GetType(unitEnumTypeAssemblyQualifiedName); - if (unitEnumType == null) - { - var ex = new UnitsNetException("Unable to find enum type."); - ex.Data["type"] = unitEnumTypeAssemblyQualifiedName; - throw ex; - } - - var unitValue = (Enum) Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram - return unitValue; + return (unitParts[0], unitParts[1]); } ///