diff --git a/src/DynamoCore/Serialization/JsonSerializationHelper.cs b/src/DynamoCore/Serialization/JsonSerializationHelper.cs new file mode 100644 index 00000000000..61d23cb6e6d --- /dev/null +++ b/src/DynamoCore/Serialization/JsonSerializationHelper.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Dynamo.Serialization +{ + /// + /// Helper class for JSON serialization using System.Text.Json. + /// Provides utilities to replace Newtonsoft.Json functionality. + /// + public static class JsonSerializationHelper + { + /// + /// Creates default JsonSerializerOptions for Dynamo serialization. + /// + /// Optional custom converters to include + /// Configured JsonSerializerOptions + public static JsonSerializerOptions CreateSerializerOptions(params JsonConverter[] converters) + { + var options = new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + PropertyNameCaseInsensitive = false, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + // Note: Using default encoder for security. If special characters need to be unescaped, + // evaluate security implications before changing to UnsafeRelaxedJsonEscaping. + }; + + // Add custom converters + if (converters != null) + { + foreach (var converter in converters) + { + options.Converters.Add(converter); + } + } + + return options; + } + + /// + /// Creates JsonSerializerOptions for deserialization with backward compatibility. + /// + /// Optional custom converters to include + /// Configured JsonSerializerOptions + public static JsonSerializerOptions CreateDeserializerOptions(params JsonConverter[] converters) + { + var options = new JsonSerializerOptions + { + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + PropertyNameCaseInsensitive = true, // More lenient for reading old files + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + // Add custom converters + if (converters != null) + { + foreach (var converter in converters) + { + options.Converters.Add(converter); + } + } + + return options; + } + + /// + /// Safely gets a string value from a JsonElement. + /// + public static string GetStringOrDefault(JsonElement element, string defaultValue = "") + { + return element.ValueKind == JsonValueKind.String ? element.GetString() ?? defaultValue : defaultValue; + } + + /// + /// Safely gets an int value from a JsonElement. + /// + public static int GetInt32OrDefault(JsonElement element, int defaultValue = 0) + { + return element.ValueKind == JsonValueKind.Number && element.TryGetInt32(out var value) ? value : defaultValue; + } + + /// + /// Safely gets a double value from a JsonElement. + /// + public static double GetDoubleOrDefault(JsonElement element, double defaultValue = 0.0) + { + return element.ValueKind == JsonValueKind.Number && element.TryGetDouble(out var value) ? value : defaultValue; + } + + /// + /// Safely gets a bool value from a JsonElement. + /// + public static bool GetBooleanOrDefault(JsonElement element, bool defaultValue = false) + { + if (element.ValueKind == JsonValueKind.True) return true; + if (element.ValueKind == JsonValueKind.False) return false; + return defaultValue; + } + + /// + /// Safely gets a Guid value from a JsonElement. + /// + public static Guid GetGuidOrDefault(JsonElement element, Guid defaultValue = default) + { + if (element.ValueKind == JsonValueKind.String) + { + var str = element.GetString(); + if (Guid.TryParse(str, out var guid)) + { + return guid; + } + } + return defaultValue; + } + + /// + /// Tries to get a property from a JsonElement. + /// + public static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement property) + { + if (element.ValueKind == JsonValueKind.Object) + { + return element.TryGetProperty(propertyName, out property); + } + property = default; + return false; + } + + /// + /// Gets an array of JsonElements from a property, or empty array if not found. + /// + public static JsonElement[] GetArrayOrEmpty(JsonElement element, string propertyName) + { + if (TryGetProperty(element, propertyName, out var property) && property.ValueKind == JsonValueKind.Array) + { + var list = new List(); + foreach (var item in property.EnumerateArray()) + { + list.Add(item); + } + return list.ToArray(); + } + return Array.Empty(); + } + + /// + /// Deserializes a JsonElement to a specific type using the provided options. + /// + /// The JsonElement to deserialize + /// Optional serializer options + /// The deserialized object, which may be null for reference types + public static T Deserialize(JsonElement element, JsonSerializerOptions options = null) + { + var json = element.GetRawText(); + return JsonSerializer.Deserialize(json, options); + } + + /// + /// Parses a JSON string and returns a JsonDocument. + /// The caller is responsible for disposing the returned JsonDocument. + /// + /// The JSON string to parse + /// A JsonDocument representing the parsed JSON + /// Thrown when the JSON is malformed + /// Thrown when json parameter is null or empty + public static JsonDocument ParseJson(string json) + { + if (string.IsNullOrEmpty(json)) + { + throw new ArgumentException("JSON string cannot be null or empty", nameof(json)); + } + + return JsonDocument.Parse(json); + } + + /// + /// Writes a JSON value with error handling. + /// + public static void WriteValue(Utf8JsonWriter writer, string propertyName, string value) + { + writer.WriteString(propertyName, value); + } + + /// + /// Writes a JSON value with error handling. + /// + public static void WriteValue(Utf8JsonWriter writer, string propertyName, int value) + { + writer.WriteNumber(propertyName, value); + } + + /// + /// Writes a JSON value with error handling. + /// + public static void WriteValue(Utf8JsonWriter writer, string propertyName, double value) + { + writer.WriteNumber(propertyName, value); + } + + /// + /// Writes a JSON value with error handling. + /// + public static void WriteValue(Utf8JsonWriter writer, string propertyName, bool value) + { + writer.WriteBoolean(propertyName, value); + } + + /// + /// Writes a JSON value with error handling. + /// + public static void WriteValue(Utf8JsonWriter writer, string propertyName, Guid value) + { + writer.WriteString(propertyName, value.ToString()); + } + } +} diff --git a/src/DynamoCoreWpf/UI/GuidedTour/CutOffArea.cs b/src/DynamoCoreWpf/UI/GuidedTour/CutOffArea.cs index b236e140859..b7ae53a8fbc 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/CutOffArea.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/CutOffArea.cs @@ -1,6 +1,6 @@ using System.Windows; using Dynamo.Core; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Dynamo.Wpf.UI.GuidedTour { @@ -20,37 +20,37 @@ public class CutOffArea : NotificationObject /// /// Since the box that cuts the elements has its size fixed, this variable applies a value to fix its Width /// - [JsonProperty(nameof(WidthBoxDelta))] + [JsonPropertyName(nameof(WidthBoxDelta))] public double WidthBoxDelta { get => widthBoxDelta; set => widthBoxDelta = value; } /// /// Since the box that cuts the elements has its size fixed, this variable applies a value to fix its Height /// - [JsonProperty(nameof(HeightBoxDelta))] + [JsonPropertyName(nameof(HeightBoxDelta))] public double HeightBoxDelta { get => heightBoxDelta; set => heightBoxDelta = value; } /// /// This property will move the CutOff area horizontally over the X axis /// - [JsonProperty(nameof(XPosOffset))] + [JsonPropertyName(nameof(XPosOffset))] public double XPosOffset { get => xPosOffset; set => xPosOffset = value; } /// /// This property will move the CutOff area vertically over the Y axis /// - [JsonProperty(nameof(YPosOffset))] + [JsonPropertyName(nameof(YPosOffset))] public double YPosOffset { get => yPosOffset; set => yPosOffset = value; } /// /// In the case the cutoff area is not the same than HostControlInfo.HostUIElementString the this property needs to be populated /// - [JsonProperty(nameof(WindowElementNameString))] + [JsonPropertyName(nameof(WindowElementNameString))] public string WindowElementNameString { get; set; } /// /// In cases when we need to put the CutOff area over a node in the Workspace this property will be used /// - [JsonProperty(nameof(NodeId))] + [JsonPropertyName(nameof(NodeId))] public string NodeId { get; set; } /// diff --git a/src/DynamoCoreWpf/UI/GuidedTour/ExitGuide.cs b/src/DynamoCoreWpf/UI/GuidedTour/ExitGuide.cs index 71b89e89ac9..07e3c63e98d 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/ExitGuide.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/ExitGuide.cs @@ -1,8 +1,8 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace Dynamo.Wpf.UI.GuidedTour @@ -12,25 +12,25 @@ internal class ExitGuide /// /// Represents the Height of Exit Guide modal /// - [JsonProperty("Height")] + [JsonPropertyName("Height")] public double Height { get; set; } /// /// Represents the Width of Exit Guide modal /// - [JsonProperty("Width")] + [JsonPropertyName("Width")] public double Width { get; set; } /// /// Represents the key to the resources related to the Title of Exit Guide modal /// - [JsonProperty("Title")] + [JsonPropertyName("Title")] public string Title { get; set; } /// /// Represents the formatted text key to the resources related to the Title of Exit Guide modal /// - [JsonProperty("FormattedText")] + [JsonPropertyName("FormattedText")] public string FormattedText { get; set; } } } diff --git a/src/DynamoCoreWpf/UI/GuidedTour/Guide.cs b/src/DynamoCoreWpf/UI/GuidedTour/Guide.cs index 874486afa6b..e8b73bc4c15 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/Guide.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/Guide.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -9,7 +10,6 @@ using Dynamo.ViewModels; using Dynamo.Wpf.Properties; using Dynamo.Wpf.Views.GuidedTour; -using Newtonsoft.Json; namespace Dynamo.Wpf.UI.GuidedTour { @@ -21,13 +21,13 @@ public class Guide /// /// This list will contain all the steps per guide read from a json file /// - [JsonProperty("GuideSteps")] + [JsonPropertyName("GuideSteps")] internal List GuideSteps { get; set; } /// /// This property represent the name of the Guide, e.g. "Get Started", "Packages" /// - [JsonProperty("Name")] + [JsonPropertyName("Name")] internal string Name { get; set; } /// @@ -36,13 +36,13 @@ public class Guide /// 1 - User interface guide /// 2 - Onboarding guide /// - [JsonProperty("SequenceOrder")] + [JsonPropertyName("SequenceOrder")] internal int SequenceOrder { get; set; } /// /// This property has the resource key string for the guide /// - [JsonProperty("GuideNameResource")] + [JsonPropertyName("GuideNameResource")] internal string GuideNameResource { get; set; } diff --git a/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs b/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs index deac15a406d..a37d72905ec 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text.Json; using System.Windows; using System.Windows.Controls; using Dynamo.Controls; @@ -12,7 +13,6 @@ using Dynamo.Wpf.Properties; using Dynamo.Wpf.ViewModels.GuidedTour; using Dynamo.Wpf.Views.GuidedTour; -using Newtonsoft.Json; using Res = Dynamo.Wpf.Properties.Resources; namespace Dynamo.Wpf.UI.GuidedTour @@ -304,8 +304,16 @@ private static List ReadGuides(string jsonFile) jsonString = r.ReadToEnd(); } + // Use case-insensitive deserialization to match JSON property names + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + //Deserialize all the information read from the json file - return JsonConvert.DeserializeObject>(jsonString); + return JsonSerializer.Deserialize>(jsonString, options); } /// diff --git a/src/DynamoCoreWpf/UI/GuidedTour/HightlightArea.cs b/src/DynamoCoreWpf/UI/GuidedTour/HightlightArea.cs index 651a6a546d4..835953bb2a5 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/HightlightArea.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/HightlightArea.cs @@ -1,6 +1,6 @@ using System.Windows; using Dynamo.Core; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Dynamo.Wpf.UI.GuidedTour { @@ -39,20 +39,20 @@ internal HighlightArea(HighlightArea copyInstance) /// /// Since the box that highlights the elements has its size fixed, this variable applies a value to fix its Width /// - [JsonProperty(nameof(WidthBoxDelta))] + [JsonPropertyName(nameof(WidthBoxDelta))] public double WidthBoxDelta { get => widthBoxDelta; set => widthBoxDelta = value; } /// /// Since the box that highlights the elements has its size fixed, this variable applies a value to fix its Height /// - [JsonProperty(nameof(HeightBoxDelta))] + [JsonPropertyName(nameof(HeightBoxDelta))] public double HeightBoxDelta { get => heightBoxDelta; set => heightBoxDelta = value; } /// /// This property will highlight the clickable area if its set to true /// - [JsonProperty(nameof(HighlightColor))] + [JsonPropertyName(nameof(HighlightColor))] public string HighlightColor { get; set; } /// @@ -74,26 +74,26 @@ public UIElement WindowElementName /// /// This property will contain the Window name in case we need to highligh a control in a different Window than DynamoView /// - [JsonProperty(nameof(WindowName))] + [JsonPropertyName(nameof(WindowName))] public string WindowName { get; set; } /// /// This represent the UIElement as string of the VisualTree that will be highlighted (readed from the json file) /// - [JsonProperty(nameof(WindowElementNameString))] + [JsonPropertyName(nameof(WindowElementNameString))] public string WindowElementNameString { get; set; } /// /// This property represent the UI Element Type that contains the UIElement (WindowElementName) due that in some cases is not the DynamoView (like the sub MenuItems) /// - [JsonProperty(nameof(UIElementTypeString))] + [JsonPropertyName(nameof(UIElementTypeString))] public string UIElementTypeString { get; set; } /// /// In case the UIElement to be highlighted is in a Grid, then this property will have the Grid name /// - [JsonProperty(nameof(UIElementGridContainer))] + [JsonPropertyName(nameof(UIElementGridContainer))] public string UIElementGridContainer { get; set; } diff --git a/src/DynamoCoreWpf/UI/GuidedTour/HostControlInfo.cs b/src/DynamoCoreWpf/UI/GuidedTour/HostControlInfo.cs index 9661d2a710b..59ebab9aca5 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/HostControlInfo.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/HostControlInfo.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Windows; using System.Windows.Controls.Primitives; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Dynamo.Wpf.UI.GuidedTour { @@ -71,19 +71,19 @@ public UIElement HostUIElement /// /// This variable will hold the name of the host (UIElement) in a string representation /// - [JsonProperty("HostUIElementString")] + [JsonPropertyName("HostUIElementString")] public string HostUIElementString { get; set; } /// /// This property will hold the placement location of the popup, for now we are just using Right, Left, Top and Bottom /// - [JsonProperty("PopupPlacement")] + [JsonPropertyName("PopupPlacement")] public PlacementMode PopupPlacement { get; set; } /// /// Once the popup host control and placecement is set we can use this property for moving the popup location Vertically (by specifying an offset) /// - [JsonProperty("VerticalPopupOffset")] + [JsonPropertyName("VerticalPopupOffset")] public double VerticalPopupOffSet { get @@ -99,7 +99,7 @@ public double VerticalPopupOffSet /// /// Once the popup host control and placecement is set we can use this property for moving the popup location Horizontally (by specifying an offset) /// - [JsonProperty("HorizontalPopupOffset")] + [JsonPropertyName("HorizontalPopupOffset")] public double HorizontalPopupOffSet { get @@ -115,31 +115,31 @@ public double HorizontalPopupOffSet /// /// Property that represents the highlight rectangle that will be shown over the Overlay /// - [JsonProperty("HighlightRectArea")] + [JsonPropertyName("HighlightRectArea")] internal HighlightArea HighlightRectArea { get; set; } /// /// Property that represents the cut off section that will be removed from the Overlay /// - [JsonProperty("CutOffRectArea")] + [JsonPropertyName("CutOffRectArea")] internal CutOffArea CutOffRectArea { get; set; } /// /// The html page that is going to be rendered inside the popup /// - [JsonProperty("HtmlPage")] + [JsonPropertyName("HtmlPage")] public HtmlPage HtmlPage { get => htmlPage; set => htmlPage = value; } /// /// This property will contain the WPF Window Name for the cases when the Popup need to be located in a different Window than DynamoView /// - [JsonProperty(nameof(WindowName))] + [JsonPropertyName(nameof(WindowName))] internal string WindowName { get; set; } /// /// This property will decide if the Popup.PlacementTarget needs to be calculated again or not (probably after UI Automation a new Window was opened) /// - [JsonProperty(nameof(DynamicHostWindow))] + [JsonPropertyName(nameof(DynamicHostWindow))] internal bool DynamicHostWindow { get; set; } } @@ -151,13 +151,13 @@ public class HtmlPage /// /// A dictionary containing the key word to be replaced in page and the filename as values /// - [JsonProperty("Resources")] + [JsonPropertyName("Resources")] public Dictionary Resources { get => resources; set => resources = value; } /// /// Filename of the HTML page /// - [JsonProperty("FileName")] + [JsonPropertyName("FileName")] public string FileName { get => fileName; set => fileName = value; } } } diff --git a/src/DynamoCoreWpf/UI/GuidedTour/Step.cs b/src/DynamoCoreWpf/UI/GuidedTour/Step.cs index 29699acd26b..ed182ab1be1 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/Step.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/Step.cs @@ -16,7 +16,7 @@ using Dynamo.ViewModels; using Dynamo.Wpf.Properties; using Dynamo.Wpf.Views.GuidedTour; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Dynamo.Wpf.UI.GuidedTour { @@ -57,73 +57,73 @@ public enum StepTypes{ SURVEY, TOOLTIP, WELCOME, EXIT_TOUR}; /// /// The step type will describe which type of window will be created after reading the json file, it can be SURVEY, TOOLTIP, WELCOME, EXIT_TOUR /// - [JsonProperty("Type")] + [JsonPropertyName("Type")] public StepTypes StepType { get; set; } /// /// The step content will contain the title and the popup content (included formatted text) /// - [JsonProperty("StepContent")] + [JsonPropertyName("StepContent")] public Content StepContent { get; set; } /// /// There are some specific Steps that will contain extra content (like Survey.RatingTextTitle), then this list will store the information /// - [JsonProperty("ExtraContent")] + [JsonPropertyName("ExtraContent")] public List StepExtraContent { get; set; } /// /// Step name, it just represent a step identifier /// - [JsonProperty("Name")] + [JsonPropertyName("Name")] public string Name { get; set; } /// /// When the Step Width property is not provided the default value will be 480 /// - [JsonProperty("Width")] + [JsonPropertyName("Width")] public double Width { get; set; } = 480; /// /// When the Step Height property is not provide the default value will be 190 /// - [JsonProperty("Height")] + [JsonPropertyName("Height")] public double Height { get; set; } = 190; /// /// Represent a sequencial numeric value for each step, when is a multiflow guide this value can be repeated /// - [JsonProperty("Sequence")] + [JsonPropertyName("Sequence")] public int Sequence { get; set; } = 0; /// /// This property contains the Host information like the host popup element or the popup position /// - [JsonProperty("HostPopupInfo")] + [JsonPropertyName("HostPopupInfo")] public HostControlInfo HostPopupInfo { get; set; } /// /// This property will hold a list of UI Automation actions (information) that will be executed when the Next or Back button are pressed /// - [JsonProperty("UIAutomation")] + [JsonPropertyName("UIAutomation")] public List UIAutomation { get; set; } /// /// This property will hold information about the methods/actions that should be executed before showing a Popup(Step) /// - [JsonProperty("PreValidation")] + [JsonPropertyName("PreValidation")] internal PreValidation PreValidationInfo { get; set; } /// /// This property will show the library if It's set to true /// - [JsonProperty("ShowLibrary")] + [JsonPropertyName("ShowLibrary")] public bool ShowLibrary { get; set; } /// - /// This propertu will hold information about the exit guide modal + /// This property will hold information about the exit guide modal /// - [JsonProperty("ExitGuide")] + [JsonPropertyName("ExitGuide")] internal ExitGuide ExitGuide { get; set; } public enum PointerDirection { TOP_RIGHT, TOP_LEFT, BOTTOM_RIGHT, BOTTOM_LEFT, BOTTOM_DOWN, NONE }; @@ -186,12 +186,12 @@ internal Popup StepUIPopup /// This property describe which will be the pointing direction of the tooltip (if is a Welcome or Survey popup then is not used) /// By default the tooltip pointer will be pointing to the left and will be located at the top /// - [JsonProperty("TooltipPointerDirection")] + [JsonPropertyName("TooltipPointerDirection")] public PointerDirection TooltipPointerDirection { get; set; } = PointerDirection.TOP_LEFT; /// /// A vertical offfset to the pointer of the popups /// - [JsonProperty("PointerVerticalOffset")] + [JsonPropertyName("PointerVerticalOffset")] public double PointerVerticalOffset { get; set; } #endregion @@ -868,13 +868,13 @@ public class Content /// /// Title of the Popup shown at the top-center /// - [JsonProperty("Title")] + [JsonPropertyName("Title")] public string Title { get; set; } /// /// The content of the Popup using a specific format for showing text, hyperlinks,images, bullet points in a RichTextBox /// - [JsonProperty("FormattedText")] + [JsonPropertyName("FormattedText")] public string FormattedText { get @@ -896,10 +896,10 @@ public string FormattedText /// public class ExtraContent { - [JsonProperty("Property")] + [JsonPropertyName("Property")] public string Property { get; set; } - [JsonProperty("Value")] + [JsonPropertyName("Value")] public string Value { get; set; } } } diff --git a/src/DynamoUtilities/DynamoFeatureFlagsManager.cs b/src/DynamoUtilities/DynamoFeatureFlagsManager.cs index 40765e493d0..6fe3938e5d1 100644 --- a/src/DynamoUtilities/DynamoFeatureFlagsManager.cs +++ b/src/DynamoUtilities/DynamoFeatureFlagsManager.cs @@ -1,11 +1,11 @@ using Dynamo.Utilities; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Reflection; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -68,14 +68,41 @@ internal void CacheAllFlags() //convert from json string to dictionary. try { - AllFlagsCache = JsonConvert.DeserializeObject>(dataFromCLI); + // System.Text.Json deserializes numbers as JsonElement by default for Dictionary + // We need to convert them to the appropriate types + var rawCache = JsonSerializer.Deserialize>(dataFromCLI); + + if (rawCache == null) + { + // No flags could be deserialized; keep cache empty and return. + RaiseMessageLogged("Failed to deserialize feature flags - received null result"); + return; + } + + AllFlagsCache = new Dictionary(); + + foreach (var kvp in rawCache) + { + var element = kvp.Value; + object value = element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.TryGetInt64(out var longVal) ? longVal : element.GetDouble(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Null => null, + _ => element.ToString() + }; + AllFlagsCache[kvp.Key] = value; + } + // Invoke the flags retrieved event on the sync context which should be the main ui thread (if in Dynamo with UI) or the default SyncContext (if in headless mode). syncContext?.Send((_) => { FlagsRetrieved?.Invoke(); }, null); - var formattedFlags = JsonConvert.SerializeObject(AllFlagsCache, Formatting.Indented); + var formattedFlags = JsonSerializer.Serialize(AllFlagsCache, new JsonSerializerOptions { WriteIndented = true }); OnLogMessage($"retrieved feature flags with value: {formattedFlags}"); } diff --git a/src/Libraries/CoreNodeModelsWpf/Charts/HeatSeriesNodeModel.cs b/src/Libraries/CoreNodeModelsWpf/Charts/HeatSeriesNodeModel.cs index 821f54a676f..3b19b08b0aa 100644 --- a/src/Libraries/CoreNodeModelsWpf/Charts/HeatSeriesNodeModel.cs +++ b/src/Libraries/CoreNodeModelsWpf/Charts/HeatSeriesNodeModel.cs @@ -14,11 +14,11 @@ using Dynamo.Controls; using Dynamo.Graph.Nodes; using Dynamo.Wpf; -using Newtonsoft.Json; using ProtoCore.AST.AssociativeAST; using DynamoServices; using Dynamo.Wpf.Properties; using Dynamo.Graph.Connectors; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Dynamo.ViewModels; diff --git a/src/Libraries/CoreNodeModelsWpf/Charts/PieChartNodeModel.cs b/src/Libraries/CoreNodeModelsWpf/Charts/PieChartNodeModel.cs index 5d364ab51c4..e403ce009df 100644 --- a/src/Libraries/CoreNodeModelsWpf/Charts/PieChartNodeModel.cs +++ b/src/Libraries/CoreNodeModelsWpf/Charts/PieChartNodeModel.cs @@ -14,12 +14,12 @@ using Dynamo.Controls; using Dynamo.Graph.Nodes; using Dynamo.Wpf; -using Newtonsoft.Json; using ProtoCore.AST.AssociativeAST; using DynamoServices; using Dynamo.Wpf.Properties; using Xceed.Wpf.Toolkit; using Dynamo.Graph.Connectors; +using Newtonsoft.Json; using Dynamo.ViewModels; namespace CoreNodeModelsWpf.Charts diff --git a/src/Libraries/CoreNodeModelsWpf/Charts/ScatterPlotNodeModel.cs b/src/Libraries/CoreNodeModelsWpf/Charts/ScatterPlotNodeModel.cs index 483c20d0371..689e7c12f90 100644 --- a/src/Libraries/CoreNodeModelsWpf/Charts/ScatterPlotNodeModel.cs +++ b/src/Libraries/CoreNodeModelsWpf/Charts/ScatterPlotNodeModel.cs @@ -14,12 +14,12 @@ using Dynamo.Controls; using Dynamo.Graph.Nodes; using Dynamo.Wpf; -using Newtonsoft.Json; using ProtoCore.AST.AssociativeAST; using DynamoServices; using Dynamo.Wpf.Properties; using ProtoCore.DesignScriptParser; using Dynamo.Graph.Connectors; +using Newtonsoft.Json; using Dynamo.ViewModels; namespace CoreNodeModelsWpf.Charts diff --git a/src/Libraries/CoreNodeModelsWpf/Charts/XYLineChartNodeModel.cs b/src/Libraries/CoreNodeModelsWpf/Charts/XYLineChartNodeModel.cs index fcb8fc8bf54..cc64daf8901 100644 --- a/src/Libraries/CoreNodeModelsWpf/Charts/XYLineChartNodeModel.cs +++ b/src/Libraries/CoreNodeModelsWpf/Charts/XYLineChartNodeModel.cs @@ -16,10 +16,10 @@ using Dynamo.Graph.Nodes; using Dynamo.ViewModels; using Dynamo.Wpf; -using Newtonsoft.Json; using ProtoCore.AST.AssociativeAST; using DynamoServices; using Dynamo.Wpf.Properties; +using Newtonsoft.Json; namespace CoreNodeModelsWpf.Charts { diff --git a/src/Notifications/NotificationCenterController.cs b/src/Notifications/NotificationCenterController.cs index 312012a851e..87ba0c7a08b 100755 --- a/src/Notifications/NotificationCenterController.cs +++ b/src/Notifications/NotificationCenterController.cs @@ -6,6 +6,7 @@ using System.Net; using System.Reflection; using System.Runtime.InteropServices; +using System.Text.Json; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; @@ -15,7 +16,6 @@ using DynamoUtilities; using Dynamo.ViewModels; using Dynamo.Wpf.ViewModels.Core; -using Newtonsoft.Json; using Microsoft.Web.WebView2.Wpf; using Dynamo.Utilities; using Dynamo.Configuration; @@ -147,7 +147,7 @@ private void WebView_NavigationCompleted(object sender, Microsoft.Web.WebView2.C private void AddNotifications(List notifications) { - var notificationsList = JsonConvert.SerializeObject(notifications); + var notificationsList = JsonSerializer.Serialize(notifications); InvokeJS($"window.setNotifications({notificationsList});"); } @@ -163,7 +163,15 @@ private void RequestNotifications() using (StreamReader reader = new StreamReader(stream)) { jsonStringFile = reader.ReadToEnd(); - notificationsModel = JsonConvert.DeserializeObject(jsonStringFile); + + // Use case-insensitive deserialization to match JSON property names + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true + }; + + notificationsModel = JsonSerializer.Deserialize(jsonStringFile, options); //We are adding a limit of months to grab the notifications var limitDate = DateTime.Now.AddMonths(-limitOfMonthsFilterNotifications); diff --git a/src/Notifications/NotificationsModel.cs b/src/Notifications/NotificationsModel.cs index f13f80a394f..62491807e62 100644 --- a/src/Notifications/NotificationsModel.cs +++ b/src/Notifications/NotificationsModel.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.Serialization; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace Dynamo.Notifications @@ -11,12 +12,15 @@ namespace Dynamo.Notifications internal class NotificationsModel { [DataMember(Name = "version")] + [JsonPropertyName("version")] internal string Version { get; set; } [DataMember(Name = "last_update_timestamp")] + [JsonPropertyName("last_update_timestamp")] internal DateTime LastUpdate { get; set; } [DataMember(Name = "notifications")] + [JsonPropertyName("notifications")] internal List Notifications { get; set; } } @@ -24,45 +28,59 @@ internal class NotificationsModel internal class NotificationItemModel { [DataMember(Name = "id")] + [JsonPropertyName("id")] internal string Id { get; set; } [DataMember(Name = "title")] + [JsonPropertyName("title")] internal string Title { get; set; } [DataMember(Name = "link")] + [JsonPropertyName("link")] internal string Link { get; set; } [DataMember(Name = "linkTitle")] + [JsonPropertyName("linkTitle")] internal string LinkTitle { get; set; } [DataMember(Name = "longDescription")] + [JsonPropertyName("longDescription")] internal string LongDescription { get; set; } [DataMember(Name = "created")] + [JsonPropertyName("created")] internal DateTime Created { get; set; } [DataMember(Name = "thumbnail")] + [JsonPropertyName("thumbnail")] internal string Thumbnail { get; set; } [DataMember(Name = "priority")] + [JsonPropertyName("priority")] internal string Priority { get; set; } [DataMember(Name = "type")] + [JsonPropertyName("type")] internal string Type { get; set; } [DataMember(Name = "scope")] + [JsonPropertyName("scope")] internal string Scope { get; set; } [DataMember(Name = "source")] + [JsonPropertyName("source")] internal string Source { get; set; } [DataMember(Name = "status")] + [JsonPropertyName("status")] internal string Status { get; set; } [DataMember(Name = "isPinned")] + [JsonPropertyName("isPinned")] internal bool IsPinned { get; set; } [DataMember(Name = "isRead")] + [JsonPropertyName("isRead")] internal bool IsRead { get; set; } = true; } }