Skip to content

Commit 52c8dba

Browse files
erioveangularsen
authored andcommitted
Modified serilization to support units saved as IComparable (#200)
* Modified serilization to support units saved as IComparable
1 parent 186b03b commit 52c8dba

File tree

2 files changed

+203
-7
lines changed

2 files changed

+203
-7
lines changed

UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonConverterTests.cs

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using System;
2323
using Newtonsoft.Json;
2424
using NUnit.Framework;
25+
using System.Collections.Generic;
2526

2627
namespace UnitsNet.Serialization.JsonNet.Tests
2728
{
@@ -226,12 +227,162 @@ public void UnitEnumChangedAfterSerialization_ExpectUnitCorrectlyDeserialized()
226227
// still deserializable, and the correct value of 1000 g is obtained.
227228
Assert.That(deserializedMass.Grams, Is.EqualTo(1000));
228229
}
230+
231+
[Test]
232+
public void UnitInIComparable_ExpectUnitCorrectlyDeserialized()
233+
{
234+
TestObjWithIComparable testObjWithIComparable = new TestObjWithIComparable()
235+
{
236+
Value = Power.FromWatts(10)
237+
};
238+
JsonSerializerSettings jsonSerializerSettings = CreateJsonSerializerSettings();
239+
240+
string json = JsonConvert.SerializeObject(testObjWithIComparable, jsonSerializerSettings);
241+
242+
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithIComparable>(json,jsonSerializerSettings);
243+
244+
Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(Power)));
245+
Assert.That((Power)deserializedTestObject.Value, Is.EqualTo(Power.FromWatts(10)));
246+
}
247+
248+
[Test]
249+
public void DoubleInIComparable_ExpectUnitCorrectlyDeserialized()
250+
{
251+
TestObjWithIComparable testObjWithIComparable = new TestObjWithIComparable()
252+
{
253+
Value = 10.0
254+
};
255+
JsonSerializerSettings jsonSerializerSettings = CreateJsonSerializerSettings();
256+
257+
string json = JsonConvert.SerializeObject(testObjWithIComparable, jsonSerializerSettings);
258+
259+
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithIComparable>(json, jsonSerializerSettings);
260+
261+
Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(double)));
262+
Assert.That((double)deserializedTestObject.Value, Is.EqualTo(10.0));
263+
}
264+
265+
[Test]
266+
public void ClassInIComparable_ExpectUnitCorrectlyDeserialized()
267+
{
268+
TestObjWithIComparable testObjWithIComparable = new TestObjWithIComparable()
269+
{
270+
Value = new ComparableClass() { Value = 10 }
271+
};
272+
JsonSerializerSettings jsonSerializerSettings = CreateJsonSerializerSettings();
273+
274+
string json = JsonConvert.SerializeObject(testObjWithIComparable, jsonSerializerSettings);
275+
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithIComparable>(json, jsonSerializerSettings);
276+
277+
Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(ComparableClass)));
278+
Assert.That(((ComparableClass)(deserializedTestObject.Value)).Value, Is.EqualTo(10.0));
279+
}
280+
281+
[Test]
282+
public void OtherObjectWithUnitAndValue_ExpectCorrectResturnValues()
283+
{
284+
TestObjWithValueAndUnit testObjWithValueAndUnit = new TestObjWithValueAndUnit()
285+
{
286+
Value = 5,
287+
Unit = "Test",
288+
};
289+
JsonSerializerSettings jsonSerializerSettings = CreateJsonSerializerSettings();
290+
291+
string json = JsonConvert.SerializeObject(testObjWithValueAndUnit, jsonSerializerSettings);
292+
TestObjWithValueAndUnit deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithValueAndUnit>(json, jsonSerializerSettings);
293+
294+
Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(double)));
295+
Assert.That(deserializedTestObject.Value, Is.EqualTo(5.0));
296+
Assert.That(deserializedTestObject.Unit, Is.EqualTo("Test"));
297+
}
298+
299+
[Test]
300+
public void ThreeObjectsInIComparableWithDifferentValues_ExpectAllCorrectlyDeserialized()
301+
{
302+
TestObjWithThreeIComparable testObjWithIComparable = new TestObjWithThreeIComparable()
303+
{
304+
Value1 = 10.0,
305+
Value2 = Power.FromWatts(19),
306+
Value3 = new ComparableClass() { Value = 10 },
307+
};
308+
JsonSerializerSettings jsonSerializerSettings = CreateJsonSerializerSettings();
309+
310+
string json = JsonConvert.SerializeObject(testObjWithIComparable, jsonSerializerSettings);
311+
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithThreeIComparable>(json, jsonSerializerSettings);
312+
313+
Assert.That(deserializedTestObject.Value1.GetType(), Is.EqualTo(typeof(double)));
314+
Assert.That((deserializedTestObject.Value1), Is.EqualTo(10.0));
315+
Assert.That(deserializedTestObject.Value2.GetType(), Is.EqualTo(typeof(Power)));
316+
Assert.That((deserializedTestObject.Value2), Is.EqualTo(Power.FromWatts(19)));
317+
Assert.That(deserializedTestObject.Value3.GetType(), Is.EqualTo(typeof(ComparableClass)));
318+
Assert.That((deserializedTestObject.Value3), Is.EqualTo(testObjWithIComparable.Value3));
319+
}
320+
321+
private static JsonSerializerSettings CreateJsonSerializerSettings()
322+
{
323+
var jsonSerializerSettings = new JsonSerializerSettings()
324+
{
325+
Formatting = Formatting.Indented,
326+
TypeNameHandling = TypeNameHandling.Auto
327+
};
328+
jsonSerializerSettings.Converters.Add(new UnitsNetJsonConverter());
329+
return jsonSerializerSettings;
330+
}
229331
}
230332

231-
internal class TestObj
333+
private class TestObj
232334
{
233335
public Frequency? NullableFrequency { get; set; }
234336
public Frequency NonNullableFrequency { get; set; }
235337
}
338+
339+
private class TestObjWithValueAndUnit : IComparable
340+
{
341+
public double Value { get; set; }
342+
public string Unit { get; set; }
343+
344+
public int CompareTo(object obj)
345+
{
346+
return Value.CompareTo(obj);
347+
}
348+
}
349+
350+
private class ComparableClass : IComparable
351+
{
352+
public int Value { get; set; }
353+
public int CompareTo(object obj)
354+
{
355+
return Value.CompareTo(obj);
356+
}
357+
358+
// Needed for virfying that the deserialized object is the same, should not affect the serilization code
359+
public override bool Equals(object obj)
360+
{
361+
if (obj == null || GetType() != obj.GetType())
362+
{
363+
return false;
364+
}
365+
return Value.Equals(((ComparableClass)obj).Value);
366+
}
367+
368+
public override int GetHashCode()
369+
{
370+
return Value.GetHashCode();
371+
}
372+
}
373+
374+
private class TestObjWithIComparable
375+
{
376+
public IComparable Value { get; set; }
377+
}
378+
379+
private class TestObjWithThreeIComparable
380+
{
381+
public IComparable Value1 { get; set; }
382+
383+
public IComparable Value2 { get; set; }
384+
385+
public IComparable Value3 { get; set; }
386+
}
236387
}
237388
}

UnitsNet.Serialization.JsonNet/UnitsNetJsonConverter.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using System.Reflection;
2626
using JetBrains.Annotations;
2727
using Newtonsoft.Json;
28+
using Newtonsoft.Json.Linq;
2829

2930
namespace UnitsNet.Serialization.JsonNet
3031
{
@@ -55,10 +56,17 @@ public class UnitsNetJsonConverter : JsonConverter
5556
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
5657
JsonSerializer serializer)
5758
{
58-
var vu = serializer.Deserialize<ValueUnit>(reader);
59-
// A null System.Nullable value was deserialized so just return null.
59+
if (reader.ValueType != null)
60+
{
61+
return reader.Value;
62+
}
63+
object obj = TryDeserializeIComparable(reader, serializer);
64+
var vu = obj as ValueUnit;
65+
// A null System.Nullable value or a comparable type was deserialized so return this
6066
if (vu == null)
61-
return null;
67+
{
68+
return obj;
69+
}
6270

6371
// "MassUnit.Kilogram" => "MassUnit" and "Kilogram"
6472
string unitEnumTypeName = vu.Unit.Split('.')[0];
@@ -102,10 +110,31 @@ where m.Name.Equals("From", StringComparison.InvariantCulture) &&
102110
// TODO: there is a possible loss of precision if base value requires higher precision than double can represent.
103111
// Example: Serializing Information.FromExabytes(100) then deserializing to Information
104112
// will likely return a very different result. Not sure how we can handle this?
105-
return fromMethod.Invoke(null, BindingFlags.Static, null, new[] {vu.Value, unit},
113+
return fromMethod.Invoke(null, BindingFlags.Static, null, new[] { vu.Value, unit },
106114
CultureInfo.InvariantCulture);
107115
}
108116

117+
private static object TryDeserializeIComparable(JsonReader reader, JsonSerializer serializer)
118+
{
119+
JToken token = JToken.Load(reader);
120+
if (!token.HasValues || token[nameof(ValueUnit.Unit)] == null || token[nameof(ValueUnit.Value)] == null)
121+
{
122+
JsonSerializer localSerializer = new JsonSerializer()
123+
{
124+
TypeNameHandling = serializer.TypeNameHandling,
125+
};
126+
return token.ToObject<IComparable>(localSerializer);
127+
}
128+
else
129+
{
130+
return new ValueUnit()
131+
{
132+
Unit = token[nameof(ValueUnit.Unit)].ToString(),
133+
Value = token[nameof(ValueUnit.Value)].ToObject<double>()
134+
};
135+
}
136+
}
137+
109138
/// <summary>
110139
/// Writes the JSON representation of the object.
111140
/// </summary>
@@ -117,6 +146,18 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
117146
{
118147
Type unitType = value.GetType();
119148

149+
// ValueUnit should be written as usual (but read in a custom way)
150+
if(unitType == typeof(ValueUnit))
151+
{
152+
JsonSerializer localSerializer = new JsonSerializer()
153+
{
154+
TypeNameHandling = serializer.TypeNameHandling,
155+
};
156+
JToken t = JToken.FromObject(value, localSerializer);
157+
158+
t.WriteTo(writer);
159+
return;
160+
}
120161
FieldInfo[] fields =
121162
unitType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
122163
if (fields.Length == 0)
@@ -184,7 +225,11 @@ public override bool CanConvert(Type objectType)
184225
return CanConvertNullable(objectType);
185226
}
186227

187-
return objectType.Namespace != null && objectType.Namespace.Equals("UnitsNet");
228+
return objectType.Namespace != null &&
229+
(objectType.Namespace.Equals(nameof(UnitsNet)) ||
230+
objectType == typeof(ValueUnit) ||
231+
// All unit types implement IComparable
232+
objectType == typeof(IComparable));
188233
}
189234

190235
/// <summary>
@@ -206,7 +251,7 @@ protected virtual bool CanConvertNullable(Type objectType)
206251
{
207252
// Need to look at the FullName in order to determine if the nullable type contains a UnitsNet type.
208253
// For example: FullName = 'System.Nullable`1[[UnitsNet.Frequency, UnitsNet, Version=3.19.0.0, Culture=neutral, PublicKeyToken=null]]'
209-
return objectType.FullName != null && objectType.FullName.Contains("UnitsNet.");
254+
return objectType.FullName != null && objectType.FullName.Contains(nameof(UnitsNet) + ".");
210255
}
211256

212257
#endregion

0 commit comments

Comments
 (0)