diff --git a/Loxone.Client.Tests/DeserializationTests.cs b/Loxone.Client.Tests/DeserializationTests.cs new file mode 100644 index 0000000..141f8a2 --- /dev/null +++ b/Loxone.Client.Tests/DeserializationTests.cs @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------- +// +// Copyright (c) The Loxone.NET Authors. All rights reserved. +// +// +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE.txt file. +// +// ---------------------------------------------------------------------- + +namespace Loxone.Client.Tests +{ + using System; + using Loxone.Client.Transport; + using Loxone.Client.Transport.Serialization.Responses; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DeserializationTests + { + [TestMethod] + public void DeserializeResponseNumberCodeLowercase() + { + string s = @"{""LL"":{""control"":""jdev/sys/getkey2/user"",""code"":200,""value"":{""key"":""0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"",""salt"":""0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345""}}}"; + var r = LXResponse.Deserialize(s); + Assert.AreEqual(200, r.Code); + Assert.AreEqual("jdev/sys/getkey2/user", r.Control); + Assert.IsNotNull(r.Value); + } + + [TestMethod] + public void DeserializeResponseStringCodeUppercase() + { + string s = @"{""LL"": { ""control"": ""dev/cfg/api"", ""value"": ""{'snr': 'AA:BB:CC:DD:EE:FF', 'version':'10.3.4.10'}"", ""Code"": ""200""}}"; + var r = LXResponse.Deserialize(s); + Assert.AreEqual(200, r.Code); + Assert.AreEqual("dev/cfg/api", r.Control); + Assert.IsNotNull(r.Value); + Assert.AreEqual(SerialNumber.Parse("AA:BB:CC:DD:EE:FF"), r.Value.SerialNumber); + Assert.AreEqual(new Version(10, 3, 4, 10), r.Value.Version); + } + } +} diff --git a/Loxone.Client.Tests/Loxone.Client.Tests.csproj b/Loxone.Client.Tests/Loxone.Client.Tests.csproj new file mode 100644 index 0000000..1fd7ccc --- /dev/null +++ b/Loxone.Client.Tests/Loxone.Client.Tests.csproj @@ -0,0 +1,14 @@ + + + netcoreapp3.1 + false + + + + + + + + + + \ No newline at end of file diff --git a/Loxone.Client/Loxone.Client.csproj b/Loxone.Client/Loxone.Client.csproj index 11e2ee4..646b82d 100644 --- a/Loxone.Client/Loxone.Client.csproj +++ b/Loxone.Client/Loxone.Client.csproj @@ -9,7 +9,7 @@ manison - + diff --git a/Loxone.Client/Properties/AssemblyInfo.cs b/Loxone.Client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5414a17 --- /dev/null +++ b/Loxone.Client/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------- +// +// Copyright (c) The Loxone.NET Authors. All rights reserved. +// +// +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE.txt file. +// +// ---------------------------------------------------------------------- + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Loxone.Client.Tests")] diff --git a/Loxone.Client/Strings.Designer.cs b/Loxone.Client/Strings.Designer.cs index 597b6a6..88c3c76 100644 --- a/Loxone.Client/Strings.Designer.cs +++ b/Loxone.Client/Strings.Designer.cs @@ -78,15 +78,6 @@ internal static string HexConverter_BadFormat { } } - /// - /// Looks up a localized string similar to Invalid date format.. - /// - internal static string LXDateTimeConverter_InvalidFormat { - get { - return ResourceManager.GetString("LXDateTimeConverter_InvalidFormat", resourceCulture); - } - } - /// /// Looks up a localized string similar to Miniserver command failed with status code {0}.. /// @@ -122,14 +113,5 @@ internal static string Uuid_ArgMustBeUuid { return ResourceManager.GetString("Uuid_ArgMustBeUuid", resourceCulture); } } - - /// - /// Looks up a localized string similar to Unexpected value when converting UUID.. - /// - internal static string UuidConverter_UnexpectedValue { - get { - return ResourceManager.GetString("UuidConverter_UnexpectedValue", resourceCulture); - } - } } } diff --git a/Loxone.Client/Strings.resx b/Loxone.Client/Strings.resx index 864a78c..bedac54 100644 --- a/Loxone.Client/Strings.resx +++ b/Loxone.Client/Strings.resx @@ -123,9 +123,6 @@ Hex string format is invalid. - - Invalid date format. - Miniserver command failed with status code {0}. @@ -135,9 +132,6 @@ Message received from Miniserver has invalid format. - - Unexpected value when converting UUID. - Argument must be of type Uuid. diff --git a/Loxone.Client/StructureFile.cs b/Loxone.Client/StructureFile.cs index 2d14f9a..919b0ff 100644 --- a/Loxone.Client/StructureFile.cs +++ b/Loxone.Client/StructureFile.cs @@ -96,16 +96,9 @@ public static async Task LoadAsync(string fileName, CancellationT public static async Task LoadAsync(Stream stream, CancellationToken cancellationToken) { - using (var reader = new StreamReader(stream)) - { - return await LoadAsync(reader, cancellationToken).ConfigureAwait(false); - } - } - - public static async Task LoadAsync(TextReader reader, CancellationToken cancellationToken) - { - string s = await reader.ReadToEndAsync().ConfigureAwait(false); - return Parse(s); + var transportFile = await Transport.Serialization.SerializationHelper.DeserializeAsync( + stream, cancellationToken).ConfigureAwait(false); + return new StructureFile(transportFile); } public async Task SaveAsync(string fileName, CancellationToken cancellationToken) @@ -116,19 +109,7 @@ public async Task SaveAsync(string fileName, CancellationToken cancellationToken } } - public async Task SaveAsync(Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream)) - { - await SaveAsync(writer, cancellationToken).ConfigureAwait(false); - } - } - - public Task SaveAsync(TextWriter writer, CancellationToken cancellationToken) - { - var serializer = Transport.Serialization.SerializationHelper.CreateSerializer(); - serializer.Serialize(writer, _innerFile); - return Task.FromResult(0); - } + public Task SaveAsync(Stream stream, CancellationToken cancellationToken) + => Transport.Serialization.SerializationHelper.SerializeAsync(stream, _innerFile, cancellationToken); } } diff --git a/Loxone.Client/Transport/Category.cs b/Loxone.Client/Transport/Category.cs index cb7464b..333059f 100644 --- a/Loxone.Client/Transport/Category.cs +++ b/Loxone.Client/Transport/Category.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -12,35 +12,22 @@ namespace Loxone.Client.Transport { using System.Drawing; using System.Collections.Generic; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; internal sealed class Category { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("uuid")] - public Uuid Uuid; + public Uuid Uuid { get; set; } - [JsonProperty("name")] - public string Name; + public string Name { get; set; } - [JsonProperty("isFavorite")] - public bool IsFavorite; + public bool IsFavorite { get; set; } - [JsonProperty("defaultRating")] - public int DefaultRating; + public int DefaultRating { get; set; } - // There should be support for Color type in .NET Standard 1.7 - [JsonProperty("color")] - public Color Color; + public Color Color { get; set; } [JsonExtensionData] - public IDictionary ExtensionData; - - #pragma warning restore CS0649 + public IDictionary ExtensionData { get; set; } } } diff --git a/Loxone.Client/Transport/Control.cs b/Loxone.Client/Transport/Control.cs index c50ddf4..d5354e8 100644 --- a/Loxone.Client/Transport/Control.cs +++ b/Loxone.Client/Transport/Control.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,43 +11,31 @@ namespace Loxone.Client.Transport { using System.Collections.Generic; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; internal sealed class Control { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("uuidAction")] - public Uuid Uuid; + [JsonPropertyName("uuidAction")] + public Uuid Uuid { get; set; } - [JsonProperty("name")] - public string Name; + public string Name { get; set; } - [JsonProperty("type")] - public string ControlType; + [JsonPropertyName("type")] + public string ControlType { get; set; } - [JsonProperty("isFavorite")] - public bool IsFavorite; + public bool IsFavorite { get; set; } - [JsonProperty("isSecured")] - public bool IsSecured; + public bool IsSecured { get; set; } - [JsonProperty("defaultRating")] - public int DefaultRating; + public int DefaultRating { get; set; } - [JsonProperty("room")] - public Uuid? Room; + public Uuid? Room { get; set; } - [JsonProperty("cat")] - public Uuid? Category; + [JsonPropertyName("cat")] + public Uuid? Category { get; set; } [JsonExtensionData] - public IDictionary ExtensionData; - - #pragma warning restore CS0649 + public IDictionary ExtensionData { get; set; } } } diff --git a/Loxone.Client/Transport/Encryptor.cs b/Loxone.Client/Transport/Encryptor.cs index 2be034f..78d2823 100644 --- a/Loxone.Client/Transport/Encryptor.cs +++ b/Loxone.Client/Transport/Encryptor.cs @@ -49,6 +49,7 @@ public string DecodeCommand(string command) byte[] encryptedResponse = Convert.FromBase64String(command); string decrypted = AesDecrypt(encryptedResponse); + decrypted = decrypted.TrimEnd('\0'); // Trim trailing zero padding. return decrypted; } diff --git a/Loxone.Client/Transport/LXResponse.cs b/Loxone.Client/Transport/LXResponse.cs index 4a05d77..29d4c9d 100644 --- a/Loxone.Client/Transport/LXResponse.cs +++ b/Loxone.Client/Transport/LXResponse.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -10,31 +10,21 @@ namespace Loxone.Client.Transport { - using Newtonsoft.Json; + using System.Text.Json.Serialization; internal sealed class LXResponse { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - private struct Root { - [JsonProperty("LL")] - public LXResponse Value; + [JsonPropertyName("LL")] + public LXResponse Value { get; set; } } - [JsonProperty("control")] - public string Control; - - [JsonProperty("Code")] - public int Code; + public string Control { get; set; } - [JsonProperty("value")] - public TValue Value; + public int Code { get; set; } - #pragma warning restore CS0649 + public TValue Value { get; set; } public static LXResponse Deserialize(string s) { diff --git a/Loxone.Client/Transport/MiniserverInfo.cs b/Loxone.Client/Transport/MiniserverInfo.cs index da7c165..56820c0 100644 --- a/Loxone.Client/Transport/MiniserverInfo.cs +++ b/Loxone.Client/Transport/MiniserverInfo.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -12,72 +12,53 @@ namespace Loxone.Client.Transport { using System; using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Serialization; using Loxone.Client.Transport.Serialization; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; internal sealed class MiniserverInfo { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("serialNr")] - public SerialNumber SerialNumber; + [JsonPropertyName("serialNr")] + public SerialNumber SerialNumber { get; set; } - [JsonProperty("msName")] - public string MiniserverName; + [JsonPropertyName("msName")] + public string MiniserverName { get; set; } - [JsonProperty("projectName")] - public string ProjectName; + public string ProjectName { get; set; } - [JsonProperty("location")] - public string Location; + public string Location { get; set; } - [JsonProperty("heatPeriodStart")] [JsonConverter(typeof(TimePeriodConverter))] - public DateTime HeatPeriodStart; + public DateTime HeatPeriodStart { get; set; } - [JsonProperty("heatPeriodEnd")] [JsonConverter(typeof(TimePeriodConverter))] - public DateTime HeatPeriodEnd; + public DateTime HeatPeriodEnd { get; set; } - [JsonProperty("coolPeriodStart")] [JsonConverter(typeof(TimePeriodConverter))] - public DateTime CoolPeriodStart; + public DateTime CoolPeriodStart { get; set; } - [JsonProperty("coolPeriodEnd")] [JsonConverter(typeof(TimePeriodConverter))] - public DateTime CoolPeriodEnd; + public DateTime CoolPeriodEnd { get; set; } - [JsonProperty("catTitle")] - public string CategoryTitle; + [JsonPropertyName("catTitle")] + public string CategoryTitle { get; set; } - [JsonProperty("roomTitle")] - public string RoomTitle; + public string RoomTitle { get; set; } - [JsonProperty("miniserverType")] - public int MiniserverType; + public int MiniserverType { get; set; } - [JsonProperty("localUrl")] - public string LocalUrl; + public string LocalUrl { get; set; } - [JsonProperty("remoteUrl")] - public string RemoteUrl; + public string RemoteUrl { get; set; } - [JsonProperty("languageCode")] - public string LanguageCode; + public string LanguageCode { get; set; } - [JsonProperty("currency")] - public string Currency; + public string Currency { get; set; } - [JsonProperty("tempUnit")] - public int TemperatureUnit; + [JsonPropertyName("tempUnit")] + public int TemperatureUnit { get; set; } [JsonExtensionData] - public IDictionary ExtensionData; - - #pragma warning restore CS0649 + public IDictionary ExtensionData { get; set; } } } diff --git a/Loxone.Client/Transport/Room.cs b/Loxone.Client/Transport/Room.cs index 74c7cfb..6d6dcd1 100644 --- a/Loxone.Client/Transport/Room.cs +++ b/Loxone.Client/Transport/Room.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,31 +11,20 @@ namespace Loxone.Client.Transport { using System.Collections.Generic; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; internal sealed class Room { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("uuid")] - public Uuid Uuid; + public Uuid Uuid { get; set; } - [JsonProperty("name")] - public string Name; + public string Name { get; set; } - [JsonProperty("isFavorite")] - public bool IsFavorite; + public bool IsFavorite { get; set; } - [JsonProperty("defaultRating")] - public int DefaultRating; + public int DefaultRating { get; set; } [JsonExtensionData] - public IDictionary ExtensionData; - - #pragma warning restore CS0649 + public IDictionary ExtensionData { get; set; } } } diff --git a/Loxone.Client/Transport/Serialization/ColorConverter.cs b/Loxone.Client/Transport/Serialization/ColorConverter.cs index ec3a871..6186369 100644 --- a/Loxone.Client/Transport/Serialization/ColorConverter.cs +++ b/Loxone.Client/Transport/Serialization/ColorConverter.cs @@ -13,29 +13,28 @@ namespace Loxone.Client.Transport.Serialization using System; using System.Drawing; using System.Globalization; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; - internal sealed class ColorConverter : JsonConverter + internal sealed class ColorConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(Color); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - string s = reader.Value as string; + string s = reader.GetString(); if (s == null || s.Length != 7 || s[0] != '#') { - throw new JsonSerializationException(Strings.ColorConverter_InvalidFormat); + throw new FormatException(Strings.ColorConverter_InvalidFormat); } - var rgb = int.Parse(s.Substring(1), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); + int rgb = Int32.Parse(s.Substring(1), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); return Color.FromArgb(rgb | unchecked((int)0xFF000000)); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options) { - var rgb = ((Color)value).ToArgb(); - writer.WriteValue( - string.Concat( + int rgb = value.ToArgb(); + writer.WriteStringValue( + String.Concat( "#", ((rgb >> 16) & 0xFF).ToString("X2", CultureInfo.InvariantCulture), ((rgb >> 8) & 0xFF).ToString("X2", CultureInfo.InvariantCulture), diff --git a/Loxone.Client/Transport/Serialization/FormattedDateTimeConverter.cs b/Loxone.Client/Transport/Serialization/FormattedDateTimeConverter.cs new file mode 100644 index 0000000..ee8514b --- /dev/null +++ b/Loxone.Client/Transport/Serialization/FormattedDateTimeConverter.cs @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------- +// +// Copyright (c) The Loxone.NET Authors. All rights reserved. +// +// +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE.txt file. +// +// ---------------------------------------------------------------------- + +namespace Loxone.Client.Transport.Serialization +{ + using System; + using System.Globalization; + using System.Text.Json; + using System.Text.Json.Serialization; + + internal class FormattedDateTimeConverter : JsonConverter + { + private readonly string _format; + + public FormattedDateTimeConverter(string format) + { + this._format = format; + } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => DateTime.ParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture); + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(_format, CultureInfo.InvariantCulture)); + } +} diff --git a/Loxone.Client/Transport/Serialization/JsonWithinStringConverter.cs b/Loxone.Client/Transport/Serialization/JsonWithinStringConverter.cs index 4dad46a..6b9fc5d 100644 --- a/Loxone.Client/Transport/Serialization/JsonWithinStringConverter.cs +++ b/Loxone.Client/Transport/Serialization/JsonWithinStringConverter.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,61 +11,18 @@ namespace Loxone.Client.Transport.Serialization { using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; - internal sealed class JsonWithinStringConverter : JsonConverter + internal sealed class JsonWithinStringConverter : JsonConverter { - private HashSet _targetTypes; - - public ICollection TargetTypes - { - get { return _targetTypes; } - } - - public JsonWithinStringConverter() - { - _targetTypes = new HashSet(); - } - - public JsonWithinStringConverter(Type targetType) - : this() - { - Contract.Requires(targetType != null); - - _targetTypes.Add(targetType); - } - - public JsonWithinStringConverter(params Type[] targetTypes) - { - Contract.Requires(targetTypes != null); - - foreach (var type in targetTypes) - { - _targetTypes.Add(type); - } - } - - public override bool CanConvert(Type objectType) => _targetTypes.Contains(objectType); - - public override bool CanRead => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - string s = reader.Value as string; - return JsonConvert.DeserializeObject( - s, - objectType, - serializer.Converters - .Where(c => c.GetType() != typeof(JsonWithinStringConverter)) - .ToArray()); + string s = reader.GetString(); + s = s.Replace('\'', '"'); + return JsonSerializer.Deserialize(s, SerializationHelper.DefaultOptions); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => new NotSupportedException(); } } diff --git a/Loxone.Client/Transport/Serialization/LXDateTimeConverter.cs b/Loxone.Client/Transport/Serialization/LXDateTimeConverter.cs index 6cff6ed..2cd0456 100644 --- a/Loxone.Client/Transport/Serialization/LXDateTimeConverter.cs +++ b/Loxone.Client/Transport/Serialization/LXDateTimeConverter.cs @@ -11,32 +11,17 @@ namespace Loxone.Client.Transport.Serialization { using System; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; - internal sealed class LXDateTimeConverter : JsonConverter + internal sealed class LXDateTimeConverter : JsonConverter { private static readonly DateTime _epochStart = new DateTime(2009, 1, 1, 0, 0, 0, DateTimeKind.Local); - public override bool CanConvert(Type objectType) => objectType == typeof(DateTime); + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => FromDouble(reader.GetDouble()); - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - switch (reader.Value) - { - case long l: - return FromInt64(l); - case double d: - return FromDouble(d); - default: - throw new JsonSerializationException(Strings.LXDateTimeConverter_InvalidFormat); - } - } - - private static DateTime FromInt64(long l) => FromDouble(l); - - private static DateTime FromDouble(double d) => _epochStart.AddSeconds(d); - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { var date = (DateTime)value; @@ -44,7 +29,9 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s date = date.ToLocalTime(); double d = date.Subtract(_epochStart).TotalSeconds; - writer.WriteValue(d); + writer.WriteNumberValue(d); } + + private static DateTime FromDouble(double d) => _epochStart.AddSeconds(d); } } diff --git a/Loxone.Client/Transport/Serialization/QuotedInt32Converter.cs b/Loxone.Client/Transport/Serialization/QuotedInt32Converter.cs new file mode 100644 index 0000000..2c7543f --- /dev/null +++ b/Loxone.Client/Transport/Serialization/QuotedInt32Converter.cs @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------- +// +// Copyright (c) The Loxone.NET Authors. All rights reserved. +// +// +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE.txt file. +// +// ---------------------------------------------------------------------- + +namespace Loxone.Client.Transport.Serialization +{ + using System; + using System.Globalization; + using System.Text.Json; + using System.Text.Json.Serialization; + + /// + /// https://github.com/dotnet/corefx/issues/39473 + /// + internal sealed class QuotedInt32Converter : JsonConverter + { + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + return Int32.Parse(reader.GetString(), CultureInfo.InvariantCulture); + } + + return reader.GetInt32(); + } + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); + } +} diff --git a/Loxone.Client/Transport/Serialization/Responses/Api.cs b/Loxone.Client/Transport/Serialization/Responses/Api.cs index d9b2099..1fcdcba 100644 --- a/Loxone.Client/Transport/Serialization/Responses/Api.cs +++ b/Loxone.Client/Transport/Serialization/Responses/Api.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,21 +11,13 @@ namespace Loxone.Client.Transport.Serialization.Responses { using System; - using Newtonsoft.Json; + using System.Text.Json.Serialization; internal sealed class Api { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("snr")] - public SerialNumber SerialNumber; - - [JsonProperty("version")] - public Version Version; + [JsonPropertyName("snr")] + public SerialNumber SerialNumber { get; set; } - #pragma warning restore CS0649 + public Version Version { get; set; } } } diff --git a/Loxone.Client/Transport/Serialization/Responses/GetKey2.cs b/Loxone.Client/Transport/Serialization/Responses/GetKey2.cs index 1b20c75..a3e91ed 100644 --- a/Loxone.Client/Transport/Serialization/Responses/GetKey2.cs +++ b/Loxone.Client/Transport/Serialization/Responses/GetKey2.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -10,24 +10,15 @@ namespace Loxone.Client.Transport.Serialization.Responses { - using Newtonsoft.Json; + using System.Text.Json.Serialization; internal sealed class GetKey2 { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("key")] - public string Key; - - [JsonProperty("salt")] - public string Salt; + public string Key { get; set; } - [JsonProperty("hashAlg")] - public string HashAlgorithm; + public string Salt { get; set; } - #pragma warning restore CS0649 + [JsonPropertyName("hashAlg")] + public string HashAlgorithm { get; set; } } } diff --git a/Loxone.Client/Transport/Serialization/Responses/GetToken.cs b/Loxone.Client/Transport/Serialization/Responses/GetToken.cs index c2b7dae..49486fb 100644 --- a/Loxone.Client/Transport/Serialization/Responses/GetToken.cs +++ b/Loxone.Client/Transport/Serialization/Responses/GetToken.cs @@ -11,31 +11,20 @@ namespace Loxone.Client.Transport.Serialization.Responses { using System; - using Newtonsoft.Json; + using System.Text.Json.Serialization; internal sealed class GetToken { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 + public string Token { get; set; } - [JsonProperty("token")] - public string Token; + public string Key { get; set; } - [JsonProperty("key")] - public string Key; - - [JsonProperty("validUntil")] [JsonConverter(typeof(LXDateTimeConverter))] - public DateTime ValidUntil; - - [JsonProperty("tokenRights")] - public int TokenRights; + public DateTime ValidUntil { get; set; } - [JsonProperty("unsecurePass")] - public bool UnsecurePassword; + public int TokenRights { get; set; } - #pragma warning restore CS0649 + [JsonPropertyName("unsecurePass")] + public bool UnsecurePassword { get; set; } } } diff --git a/Loxone.Client/Transport/Serialization/SerialNumberConverter.cs b/Loxone.Client/Transport/Serialization/SerialNumberConverter.cs index c0e17b4..4b0fe3b 100644 --- a/Loxone.Client/Transport/Serialization/SerialNumberConverter.cs +++ b/Loxone.Client/Transport/Serialization/SerialNumberConverter.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,22 +11,15 @@ namespace Loxone.Client.Transport.Serialization { using System; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; - internal sealed class SerialNumberConverter : JsonConverter + internal sealed class SerialNumberConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(SerialNumber); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - string s = reader.Value as string; - return SerialNumber.Parse(s); - } + public override SerialNumber Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => SerialNumber.Parse(reader.GetString()); - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - // Just use SerialNumber.ToString method. - writer.WriteValue(value.ToString()); - } + public override void Write(Utf8JsonWriter writer, SerialNumber value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); } } diff --git a/Loxone.Client/Transport/Serialization/SerializationHelper.cs b/Loxone.Client/Transport/Serialization/SerializationHelper.cs index a0721e3..eab1d45 100644 --- a/Loxone.Client/Transport/Serialization/SerializationHelper.cs +++ b/Loxone.Client/Transport/Serialization/SerializationHelper.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,47 +11,61 @@ namespace Loxone.Client.Transport.Serialization { using System.IO; - using Newtonsoft.Json; + using System.Text.Encodings.Web; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Threading; + using System.Threading.Tasks; using Loxone.Client.Transport.Serialization.Responses; internal static class SerializationHelper { - private static JsonConverter[] _converters = new JsonConverter[] + private static readonly JsonConverter[] _defaultConvertes = new JsonConverter[] { - new JsonWithinStringConverter(typeof(Api)), new SerialNumberConverter(), new UuidConverter(), new ColorConverter(), + new VersionConverter(), + new QuotedInt32Converter(), + new FormattedDateTimeConverter("yyyy'-'MM'-'dd' 'HH':'mm':'ss"), + }; - public static JsonSerializer CreateSerializer() + private static readonly JsonSerializerOptions _options = InitializeOptions(); + + internal static readonly JsonSerializerOptions DefaultOptions = CreateDefaultOptions(); + + private static JsonSerializerOptions CreateDefaultOptions() { - var serializer = new JsonSerializer(); - serializer.DateFormatString = "yyyy'-'MM'-'dd' 'HH':'mm':'ss"; + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; - for (int i = 0; i < _converters.Length; i++) + for (int i = 0; i < _defaultConvertes.Length; i++) { - serializer.Converters.Add(_converters[i]); + options.Converters.Add(_defaultConvertes[i]); } - return serializer; + return options; } - public static T Deserialize(TextReader reader) + private static JsonSerializerOptions InitializeOptions() { - var serializer = CreateSerializer(); - using (var jsonReader = new JsonTextReader(reader) { CloseInput = false }) - { - return serializer.Deserialize(jsonReader); - } + var options = CreateDefaultOptions(); + options.Converters.Add(new JsonWithinStringConverter()); + return options; } - public static T Deserialize(string s) - { - using (var reader = new StringReader(s)) - { - return Deserialize(reader); - } - } + public static T Deserialize(string s) => JsonSerializer.Deserialize(s, _options); + + public static ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken) + => JsonSerializer.DeserializeAsync(stream, _options, cancellationToken); + + public static Task SerializeAsync(Stream stream, T value, CancellationToken cancellationToken) + => JsonSerializer.SerializeAsync(stream, value, _options, cancellationToken); } } diff --git a/Loxone.Client/Transport/Serialization/TimePeriodConverter.cs b/Loxone.Client/Transport/Serialization/TimePeriodConverter.cs index fc13ecb..372f821 100644 --- a/Loxone.Client/Transport/Serialization/TimePeriodConverter.cs +++ b/Loxone.Client/Transport/Serialization/TimePeriodConverter.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -10,25 +10,10 @@ namespace Loxone.Client.Transport.Serialization { - using System; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - - internal sealed class TimePeriodConverter : IsoDateTimeConverter + internal sealed class TimePeriodConverter : FormattedDateTimeConverter { - public TimePeriodConverter() - { - DateTimeFormat = "MM'-'dd"; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return base.ReadJson(reader, objectType, existingValue, serializer); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public TimePeriodConverter() : base("MM'-'dd") { - base.WriteJson(writer, value, serializer); } } } diff --git a/Loxone.Client/Transport/Serialization/UuidConverter.cs b/Loxone.Client/Transport/Serialization/UuidConverter.cs index 007ebbd..9db73f7 100644 --- a/Loxone.Client/Transport/Serialization/UuidConverter.cs +++ b/Loxone.Client/Transport/Serialization/UuidConverter.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -11,31 +11,15 @@ namespace Loxone.Client.Transport.Serialization { using System; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; - internal sealed class UuidConverter : JsonConverter + internal sealed class UuidConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(Uuid) || objectType == typeof(Uuid?); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - bool nullable = objectType == typeof(Uuid?); - - if (reader.TokenType == JsonToken.Null && nullable) - { - return null; - } - else if (reader.TokenType == JsonToken.String) - { - return Uuid.Parse(reader.Value as string); - } - - throw new JsonSerializationException(Strings.UuidConverter_UnexpectedValue); - } + public override Uuid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => Uuid.Parse(reader.GetString()); - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } + public override void Write(Utf8JsonWriter writer, Uuid value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); } } diff --git a/Loxone.Client/Transport/Serialization/VersionConverter.cs b/Loxone.Client/Transport/Serialization/VersionConverter.cs new file mode 100644 index 0000000..223c1d9 --- /dev/null +++ b/Loxone.Client/Transport/Serialization/VersionConverter.cs @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------- +// +// Copyright (c) The Loxone.NET Authors. All rights reserved. +// +// +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE.txt file. +// +// ---------------------------------------------------------------------- + +namespace Loxone.Client.Transport.Serialization +{ + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + + internal sealed class VersionConverter : JsonConverter + { + public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => Version.Parse(reader.GetString()); + + public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); + } +} diff --git a/Loxone.Client/Transport/StructureFile.cs b/Loxone.Client/Transport/StructureFile.cs index cc3b328..41a2454 100644 --- a/Loxone.Client/Transport/StructureFile.cs +++ b/Loxone.Client/Transport/StructureFile.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- // // Copyright (c) The Loxone.NET Authors. All rights reserved. // @@ -12,34 +12,24 @@ namespace Loxone.Client.Transport { using System; using System.Collections.Generic; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; internal sealed class StructureFile { - // Suppress 'Field is never assigned to, and will always have its - // default value' warning. - // Justification: Fields are set during deserialization. - #pragma warning disable CS0649 - - [JsonProperty("lastModified")] - public DateTime LastModified; + public DateTime LastModified { get; set; } - [JsonProperty("msInfo")] - public MiniserverInfo MiniserverInfo; + [JsonPropertyName("msInfo")] + public MiniserverInfo MiniserverInfo { get; set; } - [JsonProperty("rooms")] - public IDictionary Rooms; + public IDictionary Rooms { get; set; } - [JsonProperty("cats")] - public IDictionary Categories; + [JsonPropertyName("cats")] + public IDictionary Categories { get; set; } - [JsonProperty("controls")] - public IDictionary Controls; + public IDictionary Controls { get; set; } [JsonExtensionData] - public IDictionary ExtensionData; - - #pragma warning restore CS0649 + public IDictionary ExtensionData { get; set; } } } diff --git a/Loxone.NET.sln b/Loxone.NET.sln index cee8b54..cb8e1c8 100644 --- a/Loxone.NET.sln +++ b/Loxone.NET.sln @@ -9,10 +9,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Loxone.Client.Samples.Conso EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BEFE450E-D373-433D-AD42-467D60454643}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig LICENSE.txt = LICENSE.txt README.md = README.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Loxone.Client.Tests", "Loxone.Client.Tests\Loxone.Client.Tests.csproj", "{A007996A-5192-4128-A955-21CAB9CF4DF3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +30,10 @@ Global {CB195BC9-96DD-4845-81CE-BF2F0E2F5D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB195BC9-96DD-4845-81CE-BF2F0E2F5D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB195BC9-96DD-4845-81CE-BF2F0E2F5D3F}.Release|Any CPU.Build.0 = Release|Any CPU + {A007996A-5192-4128-A955-21CAB9CF4DF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A007996A-5192-4128-A955-21CAB9CF4DF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A007996A-5192-4128-A955-21CAB9CF4DF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A007996A-5192-4128-A955-21CAB9CF4DF3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE