Skip to content

Commit 89a1d0e

Browse files
giantmustacheMarnix Kraus
andauthored
json: Support custom quantities (#935)
* Add possibility to register custom types to the JsonConverter The Activator is used to instantiate objects, instead of calling Quantity.From. The assumption is that there will be a constructor of new T(double value, TUnit unit). Co-authored-by: Marnix Kraus <[email protected]>
1 parent f2079bf commit 89a1d0e

File tree

3 files changed

+96
-18
lines changed

3 files changed

+96
-18
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Newtonsoft.Json;
2+
using UnitsNet.Serialization.JsonNet;
3+
using Xunit;
4+
5+
namespace UnitsNet.Tests.CustomQuantities
6+
{
7+
public class HowMuchTests
8+
{
9+
[Fact]
10+
public static void SerializeAndDeserializeCreatesSameObjectForIQuantity()
11+
{
12+
var jsonSerializerSettings = new JsonSerializerSettings { Formatting = Formatting.Indented };
13+
var quantityConverter = new UnitsNetIQuantityJsonConverter();
14+
quantityConverter.RegisterCustomType(typeof(HowMuch), typeof(HowMuchUnit));
15+
jsonSerializerSettings.Converters.Add(quantityConverter);
16+
17+
var quantity = new HowMuch(12.34, HowMuchUnit.ATon);
18+
19+
var serializedQuantity = JsonConvert.SerializeObject(quantity, jsonSerializerSettings);
20+
21+
var deserializedQuantity = JsonConvert.DeserializeObject<HowMuch>(serializedQuantity, jsonSerializerSettings);
22+
Assert.Equal(quantity, deserializedQuantity);
23+
}
24+
}
25+
}

UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
<ItemGroup>
2525
<ProjectReference Include="..\UnitsNet.Serialization.JsonNet\UnitsNet.Serialization.JsonNet.csproj" />
26+
<ProjectReference Include="..\UnitsNet.Tests\UnitsNet.Tests.csproj" />
2627
</ItemGroup>
2728

2829
</Project>

UnitsNet.Serialization.JsonNet/UnitsNetBaseJsonConverter.cs

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
33

44
using System;
5+
using System.Collections.Concurrent;
56
using System.Globalization;
67
using System.Linq;
78
using JetBrains.Annotations;
@@ -17,6 +18,29 @@ namespace UnitsNet.Serialization.JsonNet
1718
/// <typeparam name="T">The type being converted. Should either be <see cref="IQuantity"/> or <see cref="IComparable"/></typeparam>
1819
public abstract class UnitsNetBaseJsonConverter<T> : JsonConverter<T>
1920
{
21+
private ConcurrentDictionary<string, (Type Quantity, Type Unit)> _registeredTypes = new();
22+
23+
/// <summary>
24+
/// Register custom types so that the converter can instantiate these quantities.
25+
/// Instead of calling <see cref="Quantity.From"/>, the <see cref="Activator"/> will be used to instantiate the object.
26+
/// It is therefore assumed that the constructor of <paramref name="quantity"/> is specified with <c>new T(double value, typeof(<paramref name="unit"/>) unit)</c>.
27+
/// Registering the same <paramref name="unit"/> multiple times, it will overwrite the one registered.
28+
/// </summary>
29+
public void RegisterCustomType(Type quantity, Type unit)
30+
{
31+
if (!typeof(T).IsAssignableFrom(quantity))
32+
{
33+
throw new ArgumentException($"The type {quantity} is not a {typeof(T)}");
34+
}
35+
36+
if (!typeof(Enum).IsAssignableFrom(unit))
37+
{
38+
throw new ArgumentException($"The type {unit} is not a {nameof(Enum)}");
39+
}
40+
41+
_registeredTypes[unit.Name] = (quantity, unit);
42+
}
43+
2044
/// <summary>
2145
/// Reads the "Unit" and "Value" properties from a JSON string
2246
/// </summary>
@@ -79,6 +103,12 @@ protected IQuantity ConvertValueUnit(ValueUnit valueUnit)
79103
}
80104

81105
var unit = GetUnit(valueUnit.Unit);
106+
var registeredQuantity = GetRegisteredType(valueUnit.Unit).Quantity;
107+
108+
if (registeredQuantity is not null)
109+
{
110+
return (IQuantity)Activator.CreateInstance(registeredQuantity, valueUnit.Value, unit);
111+
}
82112

83113
return valueUnit switch
84114
{
@@ -87,7 +117,45 @@ protected IQuantity ConvertValueUnit(ValueUnit valueUnit)
87117
};
88118
}
89119

90-
private static Enum GetUnit(string unit)
120+
private (Type Quantity, Type Unit) GetRegisteredType(string unit)
121+
{
122+
(var unitEnumTypeName, var _) = SplitUnitString(unit);
123+
if (_registeredTypes.TryGetValue(unitEnumTypeName, out var registeredType))
124+
{
125+
return registeredType;
126+
}
127+
128+
return (null, null);
129+
}
130+
131+
private Enum GetUnit(string unit)
132+
{
133+
(var unitEnumTypeName, var unitEnumValue) = SplitUnitString(unit);
134+
135+
// First try to find the name in the list of registered types.
136+
var unitEnumType = GetRegisteredType(unit).Unit;
137+
138+
if (unitEnumType is null)
139+
{
140+
// "UnitsNet.Units.MassUnit,UnitsNet"
141+
var unitEnumTypeAssemblyQualifiedName = "UnitsNet.Units." + unitEnumTypeName + ",UnitsNet";
142+
143+
// -- see http://stackoverflow.com/a/6465096/1256096 for details
144+
unitEnumType = Type.GetType(unitEnumTypeAssemblyQualifiedName);
145+
146+
if (unitEnumType is null)
147+
{
148+
var ex = new UnitsNetException("Unable to find enum type.");
149+
ex.Data["type"] = unitEnumTypeAssemblyQualifiedName;
150+
throw ex;
151+
}
152+
}
153+
154+
var unitValue = (Enum) Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram
155+
return unitValue;
156+
}
157+
158+
private static (string EnumName, string EnumValue) SplitUnitString(string unit)
91159
{
92160
var unitParts = unit.Split('.');
93161

@@ -99,23 +167,7 @@ private static Enum GetUnit(string unit)
99167
}
100168

101169
// "MassUnit.Kilogram" => "MassUnit" and "Kilogram"
102-
var unitEnumTypeName = unitParts[0];
103-
var unitEnumValue = unitParts[1];
104-
105-
// "UnitsNet.Units.MassUnit,UnitsNet"
106-
var unitEnumTypeAssemblyQualifiedName = "UnitsNet.Units." + unitEnumTypeName + ",UnitsNet";
107-
108-
// -- see http://stackoverflow.com/a/6465096/1256096 for details
109-
var unitEnumType = Type.GetType(unitEnumTypeAssemblyQualifiedName);
110-
if (unitEnumType == null)
111-
{
112-
var ex = new UnitsNetException("Unable to find enum type.");
113-
ex.Data["type"] = unitEnumTypeAssemblyQualifiedName;
114-
throw ex;
115-
}
116-
117-
var unitValue = (Enum) Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram
118-
return unitValue;
170+
return (unitParts[0], unitParts[1]);
119171
}
120172

121173
/// <summary>

0 commit comments

Comments
 (0)