Skip to content

Commit

Permalink
Apply JsonSerializerSettings to hub device (#3107)
Browse files Browse the repository at this point in the history
  • Loading branch information
brycewang-microsoft authored Feb 13, 2023
1 parent f5b5b07 commit c1d6e97
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 9 deletions.
1 change: 0 additions & 1 deletion common/src/service/HttpClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
using Microsoft.Azure.Devices.Common.Extensions;
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;

using static Microsoft.Azure.Devices.Shared.HttpMessageHelper;

namespace Microsoft.Azure.Devices
Expand Down
1 change: 0 additions & 1 deletion e2e/test/iothub/method/MethodE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.Net;
using System.Text;
Expand Down
87 changes: 86 additions & 1 deletion e2e/test/iothub/twin/TwinE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client;
Expand All @@ -11,6 +12,7 @@
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.Devices.E2ETests.Twins
{
Expand All @@ -23,7 +25,7 @@ public class TwinE2ETests : E2EMsTestBase

private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IotHub.ConnectionString);

private static readonly List<object> s_listOfPropertyValues = new List<object>
private static readonly List<object> s_listOfPropertyValues = new()
{
1,
"someString",
Expand All @@ -35,6 +37,15 @@ public class TwinE2ETests : E2EMsTestBase
}
};

private static readonly TestDateTime s_dateTimeProperty = new()
{
Iso8601String = new DateTimeOffset(638107582284599400, TimeSpan.FromHours(1)).ToString("o", CultureInfo.InvariantCulture)
};

// ISO 8601 date time string with trailing zeros in the microseconds portion. This is to verify the Newtonsoft.Json known issue is worked around in the SDK.
// See https://github.com/JamesNK/Newtonsoft.Json/issues/1511 for more details about the known issue.
private static readonly string s_iso8601DateTimeString = "2023-01-31T10:37:08.4599400+01:00";

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task Twin_DeviceSetsReportedPropertyAndGetsItBack_Mqtt()
Expand Down Expand Up @@ -315,6 +326,42 @@ await Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetAsync(
.ConfigureAwait(false);
}

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGet_DateTimeProperties_Mqtt()
{
await Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetDateTimePropertiesAsync(
Client.TransportType.Mqtt_Tcp_Only)
.ConfigureAwait(false);
}

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGet_DateTimeProperties_MqttWs()
{
await Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetDateTimePropertiesAsync(
Client.TransportType.Mqtt_WebSocket_Only)
.ConfigureAwait(false);
}

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGet_DateTimeProperties_Amqp()
{
await Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetDateTimePropertiesAsync(
Client.TransportType.Amqp_Tcp_Only)
.ConfigureAwait(false);
}

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGet_DateTimeProperties_AmqpWs()
{
await Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetDateTimePropertiesAsync(
Client.TransportType.Amqp_WebSocket_Only)
.ConfigureAwait(false);
}

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task Twin_DeviceSetsReportedPropertyAndServiceReceivesIt_Mqtt()
Expand Down Expand Up @@ -656,6 +703,39 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetAs
await registryManager.CloseAsync().ConfigureAwait(false);
}

private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetDateTimePropertiesAsync(Client.TransportType transport)
{
string jsonString = JsonConvert.SerializeObject(s_dateTimeProperty);

using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(_devicePrefix).ConfigureAwait(false);
using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IotHub.ConnectionString);
using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport);

var twinPatch = new Twin();

// Here is an example to create a TwinCollection with json object if the users use date-formatted
// string and don't want to drop the trailing zeros in the microseconds portion while parsing the
// json properties. The Newtonsoft.Json serializer settings added in SDK apply to serial-/deserialization
// during the client operations. Therefore, to ensure the datetime has the trailing zeros and this
// test can verify the returned peoperties not dropping them, manually set up "DateParseHandling.None"
// while creating a new TwinCollection object as the twin property.
JObject jobjectProperty = JsonConvert.DeserializeObject<JObject>(
jsonString,
new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None
});

twinPatch.Properties.Desired = new TwinCollection(jobjectProperty, null);
await registryManager.UpdateTwinAsync(testDevice.Id, twinPatch, "*").ConfigureAwait(false);

Twin deviceTwin = await deviceClient.GetTwinAsync().ConfigureAwait(false);
Assert.AreEqual<string>(deviceTwin.Properties.Desired["Iso8601String"].ToString(), s_iso8601DateTimeString);

await deviceClient.CloseAsync().ConfigureAwait(false);
await registryManager.CloseAsync().ConfigureAwait(false);
}

private async Task Twin_DeviceSetsReportedPropertyAndServiceReceivesItAsync(Client.TransportType transport)
{
string propName = Guid.NewGuid().ToString();
Expand Down Expand Up @@ -768,4 +848,9 @@ internal class CustomTwinProperty

public string Name { get; set; }
}

internal class TestDateTime
{
public string Iso8601String { get; set; }
}
}
3 changes: 3 additions & 0 deletions iothub/device/src/InternalClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.IO;
using Microsoft.Azure.Devices.Client.Exceptions;
using System.ComponentModel;
using Newtonsoft.Json;

#if NET451

Expand Down Expand Up @@ -161,6 +162,8 @@ public InternalClient(
if (Logging.IsEnabled)
Logging.Enter(this, transportSettings, pipelineBuilder, nameof(InternalClient) + "_ctor");

JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetJsonSerializerSettingsDelegate();

TlsVersions.Instance.SetLegacyAcceptableVersions();

_transportSettings = transportSettings;
Expand Down
36 changes: 36 additions & 0 deletions iothub/device/src/JsonSerializerSettingsInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Client
{
/// <summary>
/// A class to initialize JsonSerializerSettings which can be applied to the project.
/// </summary>
internal static class JsonSerializerSettingsInitializer
{
/// <summary>
/// A static instance of JsonSerializerSettings which sets DateParseHandling to None.
/// </summary>
/// <remarks>
/// By default, serializing/deserializing with Newtonsoft.Json will try to parse date-formatted
/// strings to a date type, which drops trailing zeros in the microseconds date portion. By
/// specifying DateParseHandling with None, the original string will be read as-is. For more details
/// about the known issue, see https://github.com/JamesNK/Newtonsoft.Json/issues/1511.
/// </remarks>
private static readonly JsonSerializerSettings s_settings = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None
};

/// <summary>
/// Returns JsonSerializerSettings Func delegate
/// </summary>
internal static Func<JsonSerializerSettings> GetJsonSerializerSettingsDelegate()
{
return () => s_settings;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,33 @@
using System;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Shared
namespace Microsoft.Azure.Devices
{
/// <summary>
/// A class to initialize JsonSerializerSettings which can be applied to the project.
/// </summary>
public static class JsonSerializerSettingsInitializer
internal static class JsonSerializerSettingsInitializer
{
/// <summary>
/// A static instance of JsonSerializerSettings which sets DateParseHandling to None.
/// </summary>
/// <remarks>
/// By default, serializing/deserializing with Newtonsoft.Json will try to parse date-formatted
/// strings to a date type, which drops trailing zeros in the microseconds date portion. By
/// specifying DateParseHandling with None, the original string will be read as-is.
/// specifying DateParseHandling with None, the original string will be read as-is. For more details
/// about the known issue, see https://github.com/JamesNK/Newtonsoft.Json/issues/1511.
/// </remarks>
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
private static readonly JsonSerializerSettings s_settings = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None
};

/// <summary>
/// Returns JsonSerializerSettings Func delegate
/// </summary>
public static Func<JsonSerializerSettings> GetJsonSerializerSettingsDelegate()
internal static Func<JsonSerializerSettings> GetJsonSerializerSettingsDelegate()
{
return () => Settings;
return () => s_settings;
}
}
}

0 comments on commit c1d6e97

Please sign in to comment.