Skip to content
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

Use invariant culture for (de)serialization #298

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.9.10] - 2024-07-18

- Fix DateTime serialization and deserialization

## [1.9.9] - 2024-07-12

- Fix enum deserialization for SendPrimitiveAsync and SendPrimitiveCollectionAsync
Expand All @@ -23,4 +27,4 @@ Refer to the following for earlier releases of libraries in the project.
1. [Serialization - JSON](./src/serialization/json/Changelog-old.md)
1. [Serialization - FORM](./src/serialization/form/Changelog-old.md)
1. [Serialization - TEXT](./src/serialization/text/Changelog-old.md)
1. [Serialization - MULTIPART](./src/serialization/multipart/Changelog-old.md)
1. [Serialization - MULTIPART](./src/serialization/multipart/Changelog-old.md)
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<!-- Common default project properties for ALL projects-->
<PropertyGroup>
<VersionPrefix>1.9.9</VersionPrefix>
<VersionPrefix>1.9.10</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- This is overidden in test projects by setting to true-->
<IsTestProject>false</IsTestProject>
Expand Down
25 changes: 24 additions & 1 deletion src/abstractions/Time.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Kiota.Abstractions
/// <summary>
/// Model to represent only the date component of a DateTime
/// </summary>
public struct Time
public struct Time : IEquatable<Time>
{
#if NET6_0_OR_GREATER
/// <summary>
Expand All @@ -26,6 +26,29 @@ public struct Time
/// <returns>A new <see cref="TimeOnly"/> structure whose hours, minutes, seconds and milliseconds are equal to those of the supplied time.</returns>
public static implicit operator TimeOnly(Time time) => new(time.DateTime.Hour, time.DateTime.Minute, time.DateTime.Second, time.DateTime.Millisecond);
#endif
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
public bool Equals(Time other) => Hour == other.Hour && Minute == other.Minute && Second == other.Second;

/// <inheritdoc />
public override bool Equals(object? o) => (o is Time other) && Equals(other);

/// <inheritdoc />
public override int GetHashCode()
{
#if NET6_0_OR_GREATER
return HashCode.Combine(Hour, Minute, Second);
#else
int hash = 17;
hash = hash * 23 + Hour.GetHashCode();
hash = hash * 23 + Minute.GetHashCode();
hash = hash * 23 + Second.GetHashCode();
baywet marked this conversation as resolved.
Show resolved Hide resolved
return hash;
#endif
}
/// <summary>
/// Create a new Time from hours, minutes, and seconds.
/// </summary>
Expand Down
30 changes: 16 additions & 14 deletions src/serialization/form/FormParseNode.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Globalization;
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
Expand Down Expand Up @@ -143,21 +143,21 @@ public IEnumerable<T> GetCollectionOfPrimitiveValues<T>()
}
}
/// <inheritdoc/>
public DateTimeOffset? GetDateTimeOffsetValue() => DateTimeOffset.TryParse(DecodedValue, out var result) ? result : null;
public DateTimeOffset? GetDateTimeOffsetValue() => DateTimeOffset.TryParse(DecodedValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) ? result : null;
/// <inheritdoc/>
public Date? GetDateValue() => DateTime.TryParse(DecodedValue, out var result) ? new Date(result) : null;
public Date? GetDateValue() => DateTime.TryParse(DecodedValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) ? new Date(result) : null;
/// <inheritdoc/>
public decimal? GetDecimalValue() => decimal.TryParse(DecodedValue, out var result) ? result : null;
public decimal? GetDecimalValue() => decimal.TryParse(DecodedValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
MartinM85 marked this conversation as resolved.
Show resolved Hide resolved
/// <inheritdoc/>
public double? GetDoubleValue() => double.TryParse(DecodedValue, out var result) ? result : null;
public double? GetDoubleValue() => double.TryParse(DecodedValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc/>
public float? GetFloatValue() => float.TryParse(DecodedValue, out var result) ? result : null;
public float? GetFloatValue() => float.TryParse(DecodedValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc/>
public Guid? GetGuidValue() => Guid.TryParse(DecodedValue, out var result) ? result : null;
/// <inheritdoc/>
public int? GetIntValue() => int.TryParse(DecodedValue, out var result) ? result : null;
public int? GetIntValue() => int.TryParse(DecodedValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc/>
public long? GetLongValue() => long.TryParse(DecodedValue, out var result) ? result : null;
public long? GetLongValue() => long.TryParse(DecodedValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc/>
public T GetObjectValue<T>(ParsableFactory<T> factory) where T : IParsable
{
Expand Down Expand Up @@ -205,7 +205,7 @@ private void AssignFieldValues<T>(T item) where T : IParsable
}

/// <inheritdoc/>
public sbyte? GetSbyteValue() => sbyte.TryParse(DecodedValue, out var result) ? result : null;
public sbyte? GetSbyteValue() => sbyte.TryParse(DecodedValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;

/// <inheritdoc/>
public string GetStringValue() => DecodedValue;
Expand All @@ -222,22 +222,24 @@ private void AssignFieldValues<T>(T item) where T : IParsable
}

/// <inheritdoc/>
public Time? GetTimeValue() => DateTime.TryParse(DecodedValue, out var result) ? new Time(result) : null;
public Time? GetTimeValue() => DateTime.TryParse(DecodedValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) ? new Time(result) : null;

/// <inheritdoc/>
#if NET5_0_OR_GREATER
IEnumerable<T?> IParseNode.GetCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>()
public IEnumerable<T?> GetCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() where T : struct, Enum
#else
IEnumerable<T?> IParseNode.GetCollectionOfEnumValues<T>()
public IEnumerable<T?> GetCollectionOfEnumValues<T>() where T : struct, Enum
#endif
{
foreach(var v in DecodedValue.Split(ComaSeparator, StringSplitOptions.RemoveEmptyEntries))
yield return EnumHelpers.GetEnumValue<T>(v);
}

/// <inheritdoc/>
#if NET5_0_OR_GREATER
T? IParseNode.GetEnumValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>()
public T? GetEnumValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() where T : struct, Enum
#else
T? IParseNode.GetEnumValue<T>()
public T? GetEnumValue<T>() where T : struct, Enum
#endif
{
return EnumHelpers.GetEnumValue<T>(DecodedValue);
Expand Down
6 changes: 4 additions & 2 deletions src/serialization/form/FormSerializationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ private void WriteAnyValue(string? key, object value)
case IEnumerable<object> coll:
WriteCollectionOfPrimitiveValues(key, coll);
break;
case byte[] coll:
WriteByteArrayValue(key, coll);
break;
case IParsable:
throw new InvalidOperationException("Form serialization does not support nested objects.");
default:
WriteStringValue(key, value.ToString());// works for Date and String types
break;

}
}

Expand Down Expand Up @@ -134,7 +136,7 @@ public void WriteCollectionOfPrimitiveValues<T>(string? key, IEnumerable<T>? val
public void WriteDateTimeOffsetValue(string? key, DateTimeOffset? value)
{
if(value.HasValue)
WriteStringValue(key, value.Value.ToString("o"));
WriteStringValue(key, value.Value.ToString("o", CultureInfo.InvariantCulture));
}
/// <inheritdoc/>
public void WriteDateValue(string? key, Date? value)
Expand Down
52 changes: 31 additions & 21 deletions src/serialization/json/JsonParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
Expand Down Expand Up @@ -148,17 +150,11 @@ public JsonParseNode(JsonElement node, KiotaJsonSerializationContext jsonSeriali
if(_jsonNode.ValueKind != JsonValueKind.String)
return null;

if(_jsonNode.TryGetDateTimeOffset(out var dateTimeOffset))
if(TryGetUsingTypeInfo(_jsonNode, _jsonSerializerContext.DateTimeOffset, out var dateTimeOffset))
MartinM85 marked this conversation as resolved.
Show resolved Hide resolved
return dateTimeOffset;

var dateTimeOffsetStr = _jsonNode.GetString();
if(string.IsNullOrEmpty(dateTimeOffsetStr))
return null;

if(DateTimeOffset.TryParse(dateTimeOffsetStr, out dateTimeOffset))
return dateTimeOffset;

return _jsonNode.Deserialize(_jsonSerializerContext.DateTimeOffset);
else if(DateTimeOffset.TryParse(_jsonNode.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dto))
return dto;
else return null;
}

/// <summary>
Expand All @@ -181,14 +177,14 @@ public JsonParseNode(JsonElement node, KiotaJsonSerializationContext jsonSeriali
/// <returns>A <see cref="Date"/> value</returns>
public Date? GetDateValue()
{
var dateString = _jsonNode.GetString();
if(string.IsNullOrEmpty(dateString))
if(_jsonNode.ValueKind != JsonValueKind.String)
return null;

if(DateTime.TryParse(dateString, out var result))
return new Date(result);

return _jsonNode.Deserialize(_jsonSerializerContext.Date);
if(TryGetUsingTypeInfo(_jsonNode, _jsonSerializerContext.Date, out var date))
return date;
else if(DateTime.TryParse(_jsonNode.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dt))
return new Date(dt);
else return null;
}

/// <summary>
Expand All @@ -197,14 +193,14 @@ public JsonParseNode(JsonElement node, KiotaJsonSerializationContext jsonSeriali
/// <returns>A <see cref="Time"/> value</returns>
public Time? GetTimeValue()
{
var dateString = _jsonNode.GetString();
if(string.IsNullOrEmpty(dateString))
if(_jsonNode.ValueKind != JsonValueKind.String)
return null;

if(DateTime.TryParse(dateString, out var result))
if(TryGetUsingTypeInfo(_jsonNode, _jsonSerializerContext.Time, out var time))
return time;
if(DateTime.TryParse(_jsonNode.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result))
return new Time(result);

return _jsonNode.Deserialize(_jsonSerializerContext.Time);
else return null;
}

/// <summary>
Expand Down Expand Up @@ -581,5 +577,19 @@ private static string ToEnumRawName<T>(string value) where T : struct, Enum

return value;
}

private static bool TryGetUsingTypeInfo<T>(JsonElement currentElement, JsonTypeInfo<T>? typeInfo, out T? deserializedValue)
{
try
{
deserializedValue = currentElement.Deserialize(typeInfo!);
return true;
}
catch(Exception)
{
deserializedValue = default;
return false;
}
}
}
}
19 changes: 10 additions & 9 deletions src/serialization/text/TextParseNode.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Helpers;
Expand Down Expand Up @@ -45,31 +46,31 @@ public TextParseNode(string? text)
public IEnumerable<T> GetCollectionOfPrimitiveValues<T>() => throw new InvalidOperationException(NoStructuredDataMessage);
#endif
/// <inheritdoc />
public DateTimeOffset? GetDateTimeOffsetValue() => DateTimeOffset.TryParse(Text, out var result) ? result : null;
public DateTimeOffset? GetDateTimeOffsetValue() => DateTimeOffset.TryParse(Text, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) ? result : null;
/// <inheritdoc />
public Date? GetDateValue() => DateTime.TryParse(Text, out var result) ? new Date(result) : null;
public Date? GetDateValue() => DateTime.TryParse(Text, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) ? new Date(result) : null;
/// <inheritdoc />
public decimal? GetDecimalValue() => decimal.TryParse(Text, out var result) ? result : null;
public decimal? GetDecimalValue() => decimal.TryParse(Text, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc />
public double? GetDoubleValue() => double.TryParse(Text, out var result) ? result : null;
public double? GetDoubleValue() => double.TryParse(Text, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc />
public float? GetFloatValue() => float.TryParse(Text, out var result) ? result : null;
public float? GetFloatValue() => float.TryParse(Text, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc />
public Guid? GetGuidValue() => Guid.TryParse(Text, out var result) ? result : null;
/// <inheritdoc />
public int? GetIntValue() => int.TryParse(Text, out var result) ? result : null;
public int? GetIntValue() => int.TryParse(Text, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc />
public long? GetLongValue() => long.TryParse(Text, out var result) ? result : null;
public long? GetLongValue() => long.TryParse(Text, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc />
public T GetObjectValue<T>(ParsableFactory<T> factory) where T : IParsable => throw new InvalidOperationException(NoStructuredDataMessage);
/// <inheritdoc />
public sbyte? GetSbyteValue() => sbyte.TryParse(Text, out var result) ? result : null;
public sbyte? GetSbyteValue() => sbyte.TryParse(Text, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : null;
/// <inheritdoc />
public string? GetStringValue() => Text;
/// <inheritdoc />
public TimeSpan? GetTimeSpanValue() => string.IsNullOrEmpty(Text) ? null : XmlConvert.ToTimeSpan(Text);
/// <inheritdoc />
public Time? GetTimeValue() => DateTime.TryParse(Text, out var result) ? new Time(result) : null;
public Time? GetTimeValue() => DateTime.TryParse(Text, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) ? new Time(result) : null;
/// <inheritdoc />
#if NET5_0_OR_GREATER
public IEnumerable<T?> GetCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() where T : struct, Enum
Expand Down
17 changes: 9 additions & 8 deletions src/serialization/text/TextSerializationWriter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using Microsoft.Kiota.Abstractions;
Expand Down Expand Up @@ -59,33 +60,33 @@ public Stream GetSerializedContent()
/// <inheritdoc />
public void WriteByteArrayValue(string? key, byte[]? value) => WriteStringValue(key, value?.Length > 0 ? Convert.ToBase64String(value) : string.Empty);
/// <inheritdoc />
public void WriteByteValue(string? key, byte? value) => WriteStringValue(key, value?.ToString());
public void WriteByteValue(string? key, byte? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteCollectionOfObjectValues<T>(string? key, IEnumerable<T>? values) where T : IParsable => throw new InvalidOperationException(TextParseNode.NoStructuredDataMessage);
/// <inheritdoc />
public void WriteCollectionOfPrimitiveValues<T>(string? key, IEnumerable<T>? values) => throw new InvalidOperationException(TextParseNode.NoStructuredDataMessage);
/// <inheritdoc />
public void WriteDateTimeOffsetValue(string? key, DateTimeOffset? value) => WriteStringValue(key, value.HasValue ? value.Value.ToString() : null);
public void WriteDateTimeOffsetValue(string? key, DateTimeOffset? value) => WriteStringValue(key, value.HasValue ? value.Value.ToString("o", CultureInfo.InvariantCulture) : null);
/// <inheritdoc />
public void WriteDateValue(string? key, Date? value) => WriteStringValue(key, value?.ToString());
/// <inheritdoc />
public void WriteDecimalValue(string? key, decimal? value) => WriteStringValue(key, value?.ToString());
public void WriteDecimalValue(string? key, decimal? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteDoubleValue(string? key, double? value) => WriteStringValue(key, value?.ToString());
public void WriteDoubleValue(string? key, double? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteFloatValue(string? key, float? value) => WriteStringValue(key, value?.ToString());
public void WriteFloatValue(string? key, float? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteGuidValue(string? key, Guid? value) => WriteStringValue(key, value?.ToString());
/// <inheritdoc />
public void WriteIntValue(string? key, int? value) => WriteStringValue(key, value?.ToString());
public void WriteIntValue(string? key, int? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteLongValue(string? key, long? value) => WriteStringValue(key, value?.ToString());
public void WriteLongValue(string? key, long? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteNullValue(string? key) => WriteStringValue(key, "null");
/// <inheritdoc />
public void WriteObjectValue<T>(string? key, T? value, params IParsable?[] additionalValuesToMerge) where T : IParsable => throw new InvalidOperationException(TextParseNode.NoStructuredDataMessage);
/// <inheritdoc />
public void WriteSbyteValue(string? key, sbyte? value) => WriteStringValue(key, value?.ToString());
public void WriteSbyteValue(string? key, sbyte? value) => WriteStringValue(key, value?.ToString(CultureInfo.InvariantCulture));
/// <inheritdoc />
public void WriteStringValue(string? key, string? value)
{
Expand Down
28 changes: 28 additions & 0 deletions tests/abstractions/TimeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using Xunit;

namespace Microsoft.Kiota.Abstractions.Tests
{
public class TimeTests
{
[Fact]
public void TestTimeEquality()
{
var time1 = new Time(10, 30, 0);
var time2 = new Time(new DateTime(2024, 7, 17, 10, 30, 0));
var time3 = new Time(12, 0, 0);

Assert.Equal(time1, time2);
Assert.NotEqual(time1, time3);
}

[Fact]
public void TestTimeToString()
{
var time = new Time(15, 45, 30);
var expectedString = "15:45:30";

Assert.Equal(expectedString, time.ToString());
}
}
}
Loading
Loading