angularsen / UnitsNet

Makes life working with units of measurement just a little bit better.
https://www.nuget.org/packages/UnitsNet/
MIT No Attribution
2.62k stars 384 forks source link

Serializing and Deserializing of quantities stored in a List<object> fails with an exception #834

Closed bitbonk closed 4 years ago

bitbonk commented 4 years ago

Describe the bug Serializing and Deserializing of quantities stored in a List fails with an exception:

AIS.TC.Common.UnitTests.ModuleRecipes.StepSequence.ModuleRecipeTests.QuantityList_CloneUsingJsonSerialization_Succeeds

System.InvalidCastException: Converting UnitsNet.Pressure to System.String is not supported.

System.InvalidCastException
Converting UnitsNet.Pressure to System.String is not supported.
   at UnitsNet.Pressure.System.IConvertible.ToType(Type conversionType, IFormatProvider provider)
   at Newtonsoft.Json.JsonWriter.ResolveConvertibleValue(IConvertible convertible, PrimitiveTypeCode& typeCode, Object& value)
   at Newtonsoft.Json.JsonWriter.WriteValue(JsonWriter writer, PrimitiveTypeCode typeCode, Object value)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializePrimitive(JsonWriter writer, Object value, JsonPrimitiveContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer)
   at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.SerializeObject(Object value)
   at AIS.TC.Common.UnitTests.ModuleRecipes.StepSequence.ModuleRecipeTests.QuantityList_CloneUsingJsonSerialization_Succeeds()

To Reproduce Steps to reproduce the behavior (just an example): Run this tests:

        private class Container
        {
            // Must be a list of objects unfortunately.
            public List<object> Quanitities { get; set; } = new List<object>();
        }

        [Fact]
        public void QuantityList_CloneUsingMessagePackSerialization_Succeeds()
        {
            var original = new Container()
            {
                Quanitities =
                {
                    Pressure.FromBars(2)
                }
            };

            var clone = JsonConvert.DeserializeObject<Container>(
                JsonConvert.SerializeObject(original),
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = { new UnitsNetIQuantityJsonConverter() }
                });

            original.Should().NotBeSameAs(clone).And.Subject.Should().BeEquivalentTo(clone);
        }

Expected behavior The test passes

Additional context In the above test I am using fluent assertions for the assertions. Because the same class is also used for MessagePack serialization, the property Quanitities must be of type List<object>

angularsen commented 4 years ago

Hi, I believe this is currently not supported. The type needs to be IQuantity for the converter you used, we also have UnitsNetIComparableJsonConverter to support IComparable, but none of these support object to my knowledge.

You could try the legacy UnitsNetJsonConverter and see if it helps, but I suspect it doesn't. If you are willing to do a pull request to add the new converter needed to support object, then I will happily assist you in getting it merged. Take a look at how the existing converters are implemented, whether it is feasible for you to do. If you do go ahead, please also add some unit tests for it.

Best, Andreas

bitbonk commented 4 years ago

Nevermind, I just figured it out. In the above code I only pass the serialization setting into the deserialize method but not into the serialize method. If I pass the same settings into both, it works.

angularsen commented 4 years ago

Great, thanks for the update!