From 97a58b4a4edfd300e93611a63d0048e660695f7c Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 20 Jan 2025 15:41:40 +0300 Subject: [PATCH] feat: migrate from newtonsoft to system.text.json --- .../Cmdlets/GetMgGraphOption.cs | 2 - .../Cmdlets/InvokeMgGraphRequest.cs | 4 +- .../Cmdlets/SetMgGraphOption.cs | 10 ++-- .../Common/GraphSessionInitializer.cs | 4 +- .../Authentication/Common/GraphSettings.cs | 17 ++++-- .../Common/GraphSettingsConverter.cs | 30 +++++------ .../Authentication/Helpers/StringUtil.cs | 53 +++++-------------- 7 files changed, 51 insertions(+), 69 deletions(-) diff --git a/src/Authentication/Authentication/Cmdlets/GetMgGraphOption.cs b/src/Authentication/Authentication/Cmdlets/GetMgGraphOption.cs index 040277f76aa..3f6d8f27b05 100644 --- a/src/Authentication/Authentication/Cmdlets/GetMgGraphOption.cs +++ b/src/Authentication/Authentication/Cmdlets/GetMgGraphOption.cs @@ -2,8 +2,6 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ -using Newtonsoft.Json.Linq; -using System.IO; using System.Management.Automation; namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets diff --git a/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs b/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs index 4e16b462486..acb951d707d 100644 --- a/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs +++ b/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs @@ -8,7 +8,6 @@ using Microsoft.Graph.PowerShell.Authentication.Models; using Microsoft.Graph.PowerShell.Authentication.Properties; using Microsoft.PowerShell.Commands; -using Newtonsoft.Json; using System; using System.Collections; using System.Globalization; @@ -18,6 +17,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using static Microsoft.Graph.PowerShell.Authentication.Helpers.AsyncHelpers; @@ -580,7 +580,7 @@ private long SetRequestContent(HttpRequestMessage request, IDictionary content) } // Covert all dictionaries to Json - var body = JsonConvert.SerializeObject(content); + var body = JsonSerializer.Serialize(content); return SetRequestContent(request, body); } diff --git a/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs b/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs index ad4f0f76a11..dd348218db7 100644 --- a/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs +++ b/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs @@ -1,10 +1,9 @@ // ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ - -using Newtonsoft.Json; using System.IO; using System.Management.Automation; +using System.Text.Json; namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets { @@ -14,6 +13,11 @@ public class SetMgGraphOption : PSCmdlet [Parameter] public bool EnableLoginByWAM { get; set; } + private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions + { + WriteIndented = true + }; + protected override void BeginProcessing() { base.BeginProcessing(); @@ -27,7 +31,7 @@ protected override void ProcessRecord() GraphSession.Instance.GraphOption.EnableWAMForMSGraph = EnableLoginByWAM; WriteDebug($"Signin by Web Account Manager (WAM) is {(EnableLoginByWAM ? "enabled" : "disabled")}."); } - File.WriteAllText(Constants.GraphOptionsFilePath, JsonConvert.SerializeObject(GraphSession.Instance.GraphOption, Formatting.Indented)); + File.WriteAllText(Constants.GraphOptionsFilePath, JsonSerializer.Serialize(GraphSession.Instance.GraphOption, jsonSerializerOptions)); } protected override void EndProcessing() diff --git a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs index 4d3d527da14..709e7ae112a 100644 --- a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs +++ b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs @@ -5,10 +5,10 @@ using Microsoft.Graph.PowerShell.Authentication.Helpers; using Microsoft.Graph.PowerShell.Authentication.Interfaces; using Microsoft.Graph.PowerShell.Authentication.Models; -using Newtonsoft.Json; using System; using System.IO; using System.Management.Automation; +using System.Text.Json; using RequestContext = Microsoft.Graph.PowerShell.Authentication.Models.RequestContext; namespace Microsoft.Graph.PowerShell.Authentication.Common @@ -40,7 +40,7 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null) if (File.Exists(Constants.GraphOptionsFilePath)) { // Deserialize the JSON into the GraphOption instance - graphOptions = JsonConvert.DeserializeObject(File.ReadAllText(Constants.GraphOptionsFilePath)); + graphOptions = JsonSerializer.Deserialize(File.ReadAllText(Constants.GraphOptionsFilePath)); } return new GraphSession diff --git a/src/Authentication/Authentication/Common/GraphSettings.cs b/src/Authentication/Authentication/Common/GraphSettings.cs index 30e6de98f71..e7334be62af 100644 --- a/src/Authentication/Authentication/Common/GraphSettings.cs +++ b/src/Authentication/Authentication/Common/GraphSettings.cs @@ -6,11 +6,12 @@ using Microsoft.Graph.PowerShell.Authentication.Helpers; using Microsoft.Graph.PowerShell.Authentication.Interfaces; using Microsoft.Graph.PowerShell.Authentication.Models; -using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Xml.Serialization; namespace Microsoft.Graph.PowerShell.Authentication.Common @@ -155,6 +156,12 @@ public void Save(IFileProvider provider) } } + private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + WriteIndented = true, + Converters = { new GraphSettingsConverter() } + }; /// /// Safely deserializes JSON string to an object. /// @@ -169,7 +176,11 @@ internal bool TryDeserializeObject(string serialization, out T result, JsonCo bool success = false; try { - result = converter == null ? JsonConvert.DeserializeObject(serialization) : JsonConvert.DeserializeObject(serialization, converter); + if (!(converter is GraphSettingsConverter)) + { + jsonSerializerOptions.Converters.Add(converter); + } + result = converter == null ? JsonSerializer.Deserialize(serialization) : JsonSerializer.Deserialize(serialization, jsonSerializerOptions); success = true; } catch (JsonException) @@ -303,7 +314,7 @@ public bool TryRemoveEnvironment(string name, out IGraphEnvironment environment) /// A JSON string. public override string ToString() { - return JsonConvert.SerializeObject(this, Formatting.Indented, new GraphSettingsConverter()); + return JsonSerializer.Serialize(this, jsonSerializerOptions); } } diff --git a/src/Authentication/Authentication/Common/GraphSettingsConverter.cs b/src/Authentication/Authentication/Common/GraphSettingsConverter.cs index 86496f62949..df2d1633be7 100644 --- a/src/Authentication/Authentication/Common/GraphSettingsConverter.cs +++ b/src/Authentication/Authentication/Common/GraphSettingsConverter.cs @@ -4,48 +4,42 @@ using Microsoft.Graph.PowerShell.Authentication.Interfaces; using Microsoft.Graph.PowerShell.Authentication.Models; -using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Microsoft.Graph.PowerShell.Authentication.Common { /// /// A for . /// - internal class GraphSettingsConverter: JsonConverter + internal class GraphSettingsConverter : JsonConverter { - public override bool CanWrite => true; - public override bool CanRead => true; - public override bool CanConvert(Type objectType) + public override IGraphEnvironment Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return objectType == typeof(IGraphEnvironment); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (objectType == typeof(IGraphEnvironment)) + if (typeToConvert == typeof(IGraphEnvironment)) { - return serializer.Deserialize(reader); + return JsonSerializer.Deserialize(ref reader, options); } - else if (objectType == typeof(Dictionary)) + else if (typeToConvert == typeof(Dictionary)) { - var tempResult = serializer.Deserialize>(reader); + var tempResult = JsonSerializer.Deserialize>(ref reader, options); var result = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var key in tempResult.Keys) { result[key] = tempResult[key]; } - return result; + return (IGraphEnvironment)(object)result; } - return serializer.Deserialize(reader); + throw new JsonException($"Cannot convert to {typeToConvert}"); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, IGraphEnvironment value, JsonSerializerOptions options) { - serializer.Serialize(writer, value); + JsonSerializer.Serialize(writer, value, options); } } } diff --git a/src/Authentication/Authentication/Helpers/StringUtil.cs b/src/Authentication/Authentication/Helpers/StringUtil.cs index bb14f7c59b5..66cc01db1c8 100644 --- a/src/Authentication/Authentication/Helpers/StringUtil.cs +++ b/src/Authentication/Authentication/Helpers/StringUtil.cs @@ -13,7 +13,8 @@ using System.Management.Automation; using System.Net; using System.Text; -using System.Text.RegularExpressions; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Microsoft.Graph.PowerShell.Authentication.Helpers { @@ -66,6 +67,13 @@ internal static string FormatDictionary(this IDictionary content) return bodyBuilder.ToString(); } + + private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; /// /// Convert a JSON string back to an object of type or /// depending on parameter . @@ -87,48 +95,15 @@ public static object ConvertFromJson(this string jsonString, bool returnHashtabl error = null; try { - // JsonConvert.DeserializeObject does not throw an exception when an invalid Json array is passed. - // This issue is being tracked by https://github.com/JamesNK/Newtonsoft.Json/issues/1930. - // To work around this, we need to identify when jsonString is a Json array, and then try to parse it via JArray.Parse(). - - // If jsonString starts with '[' (ignoring white spaces). - if (Regex.Match(jsonString, @"^\s*\[").Success) + if (maxDepth != null) { - // JArray.Parse() will throw a JsonException if the array is invalid. - // This will be caught by the catch block below, and then throw an - // ArgumentException - this is done to have same behavior as the JavaScriptSerializer. - JArray.Parse(jsonString); - - // Please note that if the Json array is valid, we don't do anything, - // we just continue the deserialization. + _jsonSerializerOptions.MaxDepth = maxDepth.Value; } - - var obj = JsonConvert.DeserializeObject( - jsonString, - new JsonSerializerSettings - { - // This TypeNameHandling setting is required to be secure. - TypeNameHandling = TypeNameHandling.None, - MetadataPropertyHandling = MetadataPropertyHandling.Ignore, - MaxDepth = maxDepth - }); - - switch (obj) + if (returnHashtable) { - case JObject dictionary: - // JObject is a IDictionary - /* Note: Do not use Ternary operator as HashTable is implicitly convertible to PsObject, thus the ternary operation below, always returns a PSObject. - * return returnHashtable ? PopulateHashTableFromJDictionary(dictionary, out error) : PopulateFromJDictionary(dictionary, new DuplicateMemberHashSet(), out error); - * https://github.com/PowerShell/PowerShell/blob/73f852da4252eabe4097ab48a7b67c5d147a01f3/src/System.Management.Automation/engine/MshObject.cs#L965 - */ - return returnHashtable - ? PopulateHashTableFromJDictionary(dictionary, out error) - : (object)PopulateFromJDictionary(dictionary, new DuplicateMemberHashSet(), out error); - case JArray list: - return returnHashtable ? PopulateHashTableFromJArray(list, out error) : (object)PopulateFromJArray(list, out error); - default: - return obj; + return JsonSerializer.Deserialize(jsonString, _jsonSerializerOptions); } + var obj = JsonSerializer.Deserialize(jsonString, _jsonSerializerOptions); } catch (JsonException je) {