From 4097cfeab4f720ac38071d38ee1dd3e6be14f8c6 Mon Sep 17 00:00:00 2001 From: jjaguirre394 Date: Sun, 20 Dec 2020 08:13:21 -0800 Subject: [PATCH] Remove Newtonsoft dependency (#13787) * Removing newtonsoft * Fixing null reference issue in parameter predictor Co-authored-by: Juan Aguirre --- .../Az.Tools.Predictor.Test/ModelEntry.cs | 12 +- .../Az.Tools.Predictor.Test/ModelFixture.cs | 7 +- .../ParameterValuePredictor.cs | 2 +- .../DictionaryTKeyVersionTValueConverter.cs | 155 ++++++++++++++++++ .../Utilities/Converters/VersionConverter.cs | 38 +++++ .../Utilities/JsonUtilities.cs | 20 +-- 6 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/DictionaryTKeyVersionTValueConverter.cs create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/VersionConverter.cs diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelEntry.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelEntry.cs index 8a263ab66b1c..39414e38cffc 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelEntry.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelEntry.cs @@ -11,40 +11,36 @@ // See the License for the specific language governing permissions and // limitations under the License. // ---------------------------------------------------------------------------------- - -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; +using System.Text.Json.Serialization; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test { /// /// Represents a command entry in the model files. /// - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class ModelEntry { /// /// The command in the model. /// - [JsonProperty("suggestion", Required = Required.Always)] + [JsonPropertyName("suggestion")] public string Command { get; set; } /// /// The description of the command in the model. /// - [JsonProperty(Required = Required.Always)] public string Description { get; set; } /// /// The prediction count in the model. /// - [JsonProperty("suggestion count", Required = Required.Always)] + [JsonPropertyName("suggestion count")] public int PredictionCount { get; set; } /// /// The history count in the model. /// - [JsonProperty("history count", Required = Required.Always)] + [JsonPropertyName("history count")] public int HistoryCount { get; set; } /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs index 8f0e8460d4a2..4f1cc4fe3303 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs @@ -12,12 +12,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using Newtonsoft.Json; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; +using System.Text.Json; using Xunit; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test @@ -54,8 +55,8 @@ public ModelFixture() var fileInfo = new FileInfo(currentLocation); var directory = fileInfo.DirectoryName; var dataDirectory = Path.Join(directory, ModelFixture.DataDirectoryName); - var commandsModelVersions= JsonConvert.DeserializeObject>>(ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.CommandsModelZip), ModelFixture.CommandsModelJson)); - var predictionsModelVersions = JsonConvert.DeserializeObject>>>(ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.PredictionsModelZip), ModelFixture.PredictionsModelJson)); + var commandsModelVersions = JsonSerializer.Deserialize>>(ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.CommandsModelZip), ModelFixture.CommandsModelJson), JsonUtilities.DefaultSerializerOptions); + var predictionsModelVersions = JsonSerializer.Deserialize>>>(ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.PredictionsModelZip), ModelFixture.PredictionsModelJson), JsonUtilities.DefaultSerializerOptions); var commandsModel = commandsModelVersions[CommandsVersionToUse]; var predictionsModel = predictionsModelVersions[PredictionsVersionToUse]; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs index e9897bfa23cf..9d2d3246a8c4 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs @@ -101,7 +101,7 @@ private void ExtractLocalParameters(System.Collections.ObjectModel.ReadOnlyColle // We need to extract the noun to construct the parameter name. var commandName = command.FirstOrDefault()?.ToString(); - var commandNoun = ParameterValuePredictor.GetAzCommandNoun(commandName).ToLower(); + var commandNoun = ParameterValuePredictor.GetAzCommandNoun(commandName)?.ToLower(); if (commandNoun == null) { return; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/DictionaryTKeyVersionTValueConverter.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/DictionaryTKeyVersionTValueConverter.cs new file mode 100644 index 000000000000..87c069b0a37d --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/DictionaryTKeyVersionTValueConverter.cs @@ -0,0 +1,155 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities.Converters +{ + internal sealed class DictionaryTKeyVersionTValueConverter : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + { + return false; + } + + if (typeToConvert.GetGenericTypeDefinition() != typeof(IDictionary<,>)) + { + return false; + } + + return typeToConvert.GetGenericArguments()[0] == typeof(Version); + } + + public override JsonConverter CreateConverter( + Type type, + JsonSerializerOptions options) + { + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(DictionaryVersionConverterInner<,>).MakeGenericType( + new Type[] { keyType, valueType }), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: new object[] { options }, + culture: null); + + return converter; + } + + private class DictionaryVersionConverterInner : + JsonConverter> + { + private readonly JsonConverter _valueConverter; + private readonly Type _keyType; + private readonly Type _valueType; + + public DictionaryVersionConverterInner(JsonSerializerOptions options) + { + // For performance, use the existing converter if available. + _valueConverter = (JsonConverter)options + .GetConverter(typeof(TValue)); + + // Cache the key and value types. + _keyType = typeof(TKey); + _valueType = typeof(TValue); + } + + public override IDictionary Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var dictionary = new Dictionary(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return dictionary; + } + + // Get the key. + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + string propertyName = reader.GetString(); + + if (!Version.TryParse(propertyName, out Version key)) + { + throw new JsonException( + $"Unable to convert \"{propertyName}\" to System.Version."); + } + + // Get the value. + TValue value; + if (_valueConverter != null) + { + reader.Read(); + value = _valueConverter.Read(ref reader, _valueType, options); + } + else + { + value = JsonSerializer.Deserialize(ref reader, options); + } + + // Add to dictionary. + dictionary.Add(key, value); + } + + throw new JsonException(); + } + + public override void Write( + Utf8JsonWriter writer, + IDictionary dictionary, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach ((Version key, TValue value) in dictionary) + { + var propertyName = key.ToString(); + writer.WritePropertyName + (options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName); + + if (_valueConverter != null) + { + _valueConverter.Write(writer, value, options); + } + else + { + JsonSerializer.Serialize(writer, value, options); + } + } + + writer.WriteEndObject(); + } + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/VersionConverter.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/VersionConverter.cs new file mode 100644 index 000000000000..5d0c6633c7c5 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Converters/VersionConverter.cs @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities.Converters +{ + internal sealed class VersionConverter : JsonConverter + { + public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (Version.TryParse(reader.GetString(), out var version)) + { + return version; + } + + throw new JsonException(); + } + + public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs index 2b8f5a0f22c9..656ab2419e30 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities.Converters; using System; using System.Text.Encodings.Web; using System.Text.Json; @@ -24,24 +25,6 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities /// internal static class JsonUtilities { - private sealed class VersionConverter : JsonConverter - { - public override Version Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (Version.TryParse(reader.GetString(), out var version)) - { - return version; - } - - throw new JsonException(); - } - - public override void Write (Utf8JsonWriter writer, Version value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } - } - /// /// The default serialization options: /// 1. Use camel case in the naming. @@ -54,6 +37,7 @@ public override void Write (Utf8JsonWriter writer, Version value, JsonSerializer { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), new VersionConverter(), + new DictionaryTKeyVersionTValueConverter() }, };