-
Notifications
You must be signed in to change notification settings - Fork 386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add [DataContract] and [DataMember] annotations #972
Add [DataContract] and [DataMember] annotations #972
Conversation
…s) to all quantities - added a set of serialization tests (common to all serializers) - added tests for the DataContractSerializer (xml) and DataContractJsonSerializer (default settings)
.. and comments
Will try to get to this soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial questions
UnitsNet.Tests/Serialization/Json/DefaultDataContractJsonSerializerTests.cs
Show resolved
Hide resolved
public void DecimalQuantity_SerializedWithDecimalValueValueAndIntegerUnit() | ||
{ | ||
var quantity = new Information(1.20m, InformationUnit.Exabyte); | ||
var expectedJson = "{\"Value\":1.20,\"Unit\":4}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I keep forgetting, but wasn't there some good reason to use quoted numbers when serializing?
At first I thought json could not represent double.MaxValue or decimal.MaxValue, but it seems just fine representing both of these in a round-trip.
I tried a quick example in linqpad:
void Main()
{
//decimal smallestDecimal = (decimal) (1 / Math.Pow(10, 28));
//double smallestDouble = double.Epsilon;
//var foo = new Foo(smallestDouble, smallestDecimal);
var foo = new Foo(double.MaxValue, decimal.MaxValue);
var systemTextJson = System.Text.Json.JsonSerializer.Serialize(foo).Dump("system.text.json");
var newtonsoftJson = JsonConvert.SerializeObject(foo).Dump("newtonsoft");
var systemTextFoo = System.Text.Json.JsonSerializer.Deserialize<Foo>(systemTextJson).Dump("systemTextDeser");
var newtonsoftFoo = JsonConvert.DeserializeObject<Foo>(newtonsoftJson).Dump("newtonsoftDeser");
systemTextFoo.Equals(foo).Dump("systemTextDeser.Equals(original)");
newtonsoftFoo.Equals(foo).Dump("newtonsoftDeser.Equals(original)");
}
public record Foo(double MyDouble, decimal MyDecimal);
Similarly, if I instead of MaxValue chose the smallest number of each, it still roundtrips just fine for both Newtonsoft and System.Text.Json.
decimal smallestDecimal = (decimal) (1 / Math.Pow(10, 28));
double smallestDouble = double.Epsilon;
var foo = new Foo(smallestDouble, smallestDecimal);
So. I can't really remember why quoted strings were used to begin with? Is it maybe not necessary after all? Do you remember?
This is the one that introduced it, but I don't really see a description of the problem it solved:
#868
Serialize decimal values as string to keep number of decimal places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this relates to the scale being lost while de-serializing using Newtonsoft - you could see it in the debugger (or in you example of ****0335.0 - that's a double right there). At the same time - this doesn't happen during serialization - the exact scale is preserved.
After doing the initial round of tests- I actually stumbled upon an option that might be able to resolve this issue- but haven't had time to actually figure out what it does: FloatParseHandling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also an option for the special floating point numbers, e.g. NaN, PositiveInfinity and NegativeInfinity - however given that none of those are really supported by UnitsNet (the Guard aggressively protecting against it) I don't think this would have been a reason to use the string representation for all numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just trying to understand better, so here is another example, now using our json.net serializer and schema:
void Main()
{
decimal smallestDecimal = (decimal) (1 / Math.Pow(10, 28));
var foo = new Foo(
LargestDecimal: new BitRate(decimal.MaxValue, BitRateUnit.BitPerSecond),
SmallestDecimal: new BitRate(smallestDecimal, BitRateUnit.BitPerSecond),
LargestDouble: new Length(double.MaxValue, LengthUnit.Meter),
SmallestDouble: new Length(double.Epsilon, LengthUnit.Meter)
).Dump("foo");
var newtonJson = JsonConvert.SerializeObject(foo, Newtonsoft.Json.Formatting.Indented, new UnitsNetIQuantityJsonConverter()).Dump("newtonJson");
//System.Text.Json.JsonSerializer.Serialize(foo).Dump("systemJson"); // throws cyclical exception
var newtonFoo = JsonConvert.DeserializeObject<Foo>(newtonJson, new UnitsNetIQuantityJsonConverter()).Dump("newtonFoo");
newtonFoo.Equals(foo).Dump("newtonFoo.Equals(foo)");
}
public record Foo(IQuantity LargestDecimal, IQuantity SmallestDecimal, IQuantity LargestDouble, IQuantity SmallestDouble);
{
"LargestDecimal": {
"Unit": "BitRateUnit.BitPerSecond",
"Value": 7.922816251426434E+28,
"ValueString": "79228162514264337593543950335",
"ValueType": "decimal"
},
"SmallestDecimal": {
"Unit": "BitRateUnit.BitPerSecond",
"Value": 1.0000000000000001E-28,
"ValueString": "0.0000000000000000000000000001",
"ValueType": "decimal"
},
"LargestDouble": {
"Unit": "LengthUnit.Meter",
"Value": 1.7976931348623157E+308
},
"SmallestDouble": {
"Unit": "LengthUnit.Meter",
"Value": 5E-324
}
}
You wrote:
I think this relates to the scale being lost while de-serializing using Newtonsoft [...] 0335.0 - that's a double right there
I think you are right, but only if we were actually trying to deserialize back into the wrong number type:
double.MaxValue
serialized to JSON and deserialized to a decimal property would probably result inOverflowException
decimal.MaxValue
serialized to JSON and deserialized to a double property, would lose precision.
However, this shouldn't be a problem when serializing decimal and double quantities if our converters do their job.
What I am beginning to realize is that the problem is not JSON itself, nor is it SystemTextJson or NewtonsoftJson. It is simply that when deserializing, we need to know whether to deserialize to double
or to decimal
. And we do have this information encoded in our JSON schema. So that leads me to think that the ValueString
property in our schema is really redundant. ValueType
should be sufficient, shouldn't it?
If I am not entirely missing something, then bringing this line of thought back to this PR; should the data contract have a ValueType
property to support deserializing decimal values without losing precision?
And, should we deprecate the ValueString
property in our json.net schema and bump the major version with a breaking change to remove that redundant property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion- the ValueType
is redundant - it can easily be determined from the Unit (I'm checking the corresponding QuantityInfo.Zero).
Unfortunately, in the case of Newtonsoft- knowing the target doesn't help. Here's a fiddle for you to consider:
JToken.Load(new JsonTextReader(new StringReader("{\"DecimalValue\":1.20}"))).Value<decimal>("DecimalValue").Dump()
Dumping object(Decimal)
1.2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All our converters use JToken.Load
, but that might be because everybody was copying over from the previous implementation..
The documentation isn't much help either- the first example uses JToken
for the WriteJson
(without implementing a corresponding ReadJson
!?).
The second one is just as useless (I'm not even sure what it is supposed to demonstrate) .
After browsing through the source code - it seems like the implementation of DeserializeObject
avoids calling JToken/JObject.Load: it's going through a while(reader.Read()) loop, parsing each token it encounters (e.g. TokenType.StartObject -> TokenType.PropertyName ... -> TokenType.EndObject).
Following that logic, I managed to deserialize my DecimalValue
- it isn't pretty, but I think we can manage..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so to recap.
- It is possible do deprecate
ValueType
andValueString
in our existing json.net schema, the converters should be able to do without since we can look up the number type fromQuantityInfo
. - This PR's data contract is already good, but, more changes are needed to support full precision of decimal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR doesn't affect the existing code in any way... It's just about the DataContract and DataMember annotations- and the tests for the corresponding DataContract serializers (xml and json - but without any of the surrogate definitions)..
I sensed you were still in the spirit of our discussion in #907 but the point of starting over was to take it one step at a time. I was of course, going to propose an implementation for a JsonConverter but in another PR.
Other than that - yes you are correct, I think we can manage without the extra properties - and yes, my current implementation (not part of this PR) only supported decimals using the "quoted" numbers schema- and needs to be re-written (once again).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should however, take a close look at the tests- as they would probably be shared between all serializers (DC, Newtonsoft, System.Text and whatever else comes after..).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I am being slow to grasp all the things, I keep forgetting details as time passes between each time I visit this topic. I think I'm once again getting the mental map decent and this PR looks ready to merge to me.
Just for my own summary:
- This PR adds the contracts to all quantities
- Contracts are supported by built-in XML/JSON serializers, newtonsoft, protobuf and a bunch of others - but currently not system.text.json
- This PR adds tests for built-in XML/JSON serializers, the same tests can be reused as you mention
- The built-in XML/JSON serializers preserve decimal scale out of the box
- Surrogates can be built to enhance/change the built-in XML/JSON serializers
- System.Text.Json custom converter can be provided separately WIP: Add serialization support for System.Text.Json #905 Add serialization support for System.Text.Json (Attempt #2) #966
- Newtonsoft JSON converter works, but its schema has redundant properties
The tests are excellent, really thorough 👏
Fixes #180 Merging the v5 release branch. It is still in alpha, but it is functional, nugets are published and there are not many planned breaking changes left. By merging, all efforts moving forward are targeting v5 and this reduces friction: - No more merge conflicts trying to forward port all changes to v5, instead cherry pick new units and fixes to v4 until v5 is fully stable. - Contributors are having trouble building v4 locally due to `net40`, `net47` and Windows Runtime Component targets. ## 💥 Breaking changes Default number format should be CultureInfo.CurrentCulture, not CurrentUICulture (#795) Use CurrentCulture rather than CurrentUICulture (#986) Return `QuantityValue` in `IQuantity` properties instead of `double` (#1074) Return `decimal` in properties of `Power`, `BitRate` and `Information` quantities (#1074) Fix singular name VolumeFlow.MillionUsGallonsPerDay ## 🔥 Removed Remove targets: net40, net47, Windows Runtime Component. Remove `Undefined` enum value for all unit enum types Remove QuantityType enum Remove IQuantity.Units and .UnitNames Remove IQuantity.ToString() overloads Remove IEquatable<T> and equality operators/methods Remove GlobalConfiguration Remove obsolete and deprecated code. Remove Molarity ctor and operator overloads Remove MinValue, MaxValue per quantity due to ambiguity Remove string format specifiers: "v", "s" json: Remove UnitsNetJsonConverter ## ✨ New QuantityValue: Implement IEquality, IComparable, IFormattable QuantityValue: 16 bytes instead of 40 bytes (#1084) Add `[DataContract]` annotations (#972) ## ♻️ Improvements Upgrade CodeGen, tests and sample apps to net6.0. ## 📝 JSON unit definition schema changes Rename `BaseType` to `ValueType`, for values "double" and "decimal". Rename `XmlDoc` to `XmlDocSummary`. ## TODO Add back `IEquatable<T>`, but implement as strict equality with tuple of quantity name + unit + value. #1017 (comment) ## Postponed for later #1067
Related to #907