Skip to content

Commit

Permalink
Modified serilization to support units saved as IComparable
Browse files Browse the repository at this point in the history
  • Loading branch information
Erik Ovegard committed Oct 28, 2016
1 parent 981e313 commit ded0ebe
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 5 deletions.
83 changes: 83 additions & 0 deletions UnitsNet.Serialization.JsonNet.Tests/UnitsNetJsonConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,95 @@ public void UnitEnumChangedAfterSerialization_ExpectUnitCorrectlyDeserialized()
// still deserializable, and the correct value of 1000 g is obtained.
Assert.That(deserializedMass.Grams, Is.EqualTo(1000));
}

[Test]
public void UnitInIComparable_ExpectUnitCorrectlyDeserialized()
{
TestObjWithIComparable testObjWithIComparable = new TestObjWithIComparable()
{
Value = Power.FromWatts(10)
};
var jsonSerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Objects
};
jsonSerializerSettings.Converters.Add(new UnitsNetJsonConverter());

string json = JsonConvert.SerializeObject(testObjWithIComparable,jsonSerializerSettings);

jsonSerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithIComparable>(json,jsonSerializerSettings);

Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(Power)));
Assert.That((Power)deserializedTestObject.Value, Is.EqualTo(Power.FromWatts(10)));
}

[Test]
public void DoubleInIComparable_ExpectUnitCorrectlyDeserialized()
{
TestObjWithIComparable testObjWithIComparable = new TestObjWithIComparable()
{
Value = 10.0
};
var jsonSerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Objects
};
jsonSerializerSettings.Converters.Add(new UnitsNetJsonConverter());

string json = JsonConvert.SerializeObject(testObjWithIComparable, jsonSerializerSettings);

jsonSerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithIComparable>(json, jsonSerializerSettings);

Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(double)));
Assert.That((double)deserializedTestObject.Value, Is.EqualTo(10.0));
}

[Test]
public void ClassInIComparable_ExpectUnitCorrectlyDeserialized()
{
TestObjWithIComparable testObjWithIComparable = new TestObjWithIComparable()
{
Value = new ComparableClass() { Value = 10 }
};
var jsonSerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Objects
};
jsonSerializerSettings.Converters.Add(new UnitsNetJsonConverter());

string json = JsonConvert.SerializeObject(testObjWithIComparable, jsonSerializerSettings);

jsonSerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
var deserializedTestObject = JsonConvert.DeserializeObject<TestObjWithIComparable>(json, jsonSerializerSettings);

Assert.That(deserializedTestObject.Value.GetType(), Is.EqualTo(typeof(ComparableClass)));
Assert.That(((ComparableClass)(deserializedTestObject.Value)).Value, Is.EqualTo(10.0));
}
}

internal class TestObj
{
public Frequency? NullableFrequency { get; set; }
public Frequency NonNullableFrequency { get; set; }
}

internal class ComparableClass : IComparable
{
public int Value { get; set; }
public int CompareTo(object obj)
{
return Value.CompareTo(obj);
}
}

internal class TestObjWithIComparable
{
public IComparable Value { get; set; }
}
}
}
56 changes: 51 additions & 5 deletions UnitsNet.Serialization.JsonNet/UnitsNetJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using System.Reflection;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace UnitsNet.Serialization.JsonNet
{
Expand Down Expand Up @@ -55,10 +56,17 @@ public class UnitsNetJsonConverter : JsonConverter
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var vu = serializer.Deserialize<ValueUnit>(reader);
// A null System.Nullable value was deserialized so just return null.
if (reader.ValueType != null)
{
return reader.Value;
}
object obj = TryDeserializeIComparable(reader, serializer);
var vu = obj as ValueUnit;
// A null System.Nullable value or a comparable type was deserialized so return this
if (vu == null)
return null;
{
return obj;
}

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

private static object TryDeserializeIComparable(JsonReader reader, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (!token.HasValues || token["Unit"] == null || token["Value"] == null)
{
JsonSerializer localSerializer = new JsonSerializer()
{
TypeNameHandling = serializer.TypeNameHandling,
};
return token.ToObject<IComparable>(localSerializer);
}
else
{
return new ValueUnit()
{
Unit = token["Unit"].ToString(),
Value = token["Value"].ToObject<double>()
};
}
}

/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
Expand All @@ -117,6 +146,18 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
{
Type unitType = value.GetType();

// ValueUnit should be written as usual (but read in a custom way)
if(unitType == typeof(ValueUnit))
{
JsonSerializer localSerializer = new JsonSerializer()
{
TypeNameHandling = serializer.TypeNameHandling,
};
JToken t = JToken.FromObject(value, localSerializer);

t.WriteTo(writer);
return;
}
FieldInfo[] fields =
unitType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (fields.Length == 0)
Expand Down Expand Up @@ -184,7 +225,12 @@ public override bool CanConvert(Type objectType)
return CanConvertNullable(objectType);
}

return objectType.Namespace != null && objectType.Namespace.Equals("UnitsNet");
return objectType.Namespace != null &&
(objectType.Namespace.Equals("UnitsNet") ||
objectType == typeof(ValueUnit) ||
// All unit types implement IComparable
objectType==typeof(IComparable) ||
objectType.FullName.StartsWith("System." + nameof(IComparable)));
}

/// <summary>
Expand Down

12 comments on commit ded0ebe

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 210 is now running

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 210 outcome was SUCCESS
Summary: Tests passed: 830 Build time: 0:0:41

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 211 is now running

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 211 outcome was SUCCESS
Summary: Tests passed: 830 Build time: 0:0:33

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 214 is now running

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 214 outcome was SUCCESS
Summary: Tests passed: 827 Build time: 0:0:29

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 215 is now running

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 215 outcome was SUCCESS
Summary: Tests passed: 830 Build time: 0:0:30

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 220 is now running

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 220 outcome was SUCCESS
Summary: Tests passed: 830 Build time: 0:0:30

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 227 is now running

@angularsen
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity Units.NET :: Build and Test Build 227 outcome was SUCCESS
Summary: Tests passed: 830 Build time: 0:0:32

Please sign in to comment.