Functions/GenXdev.Helpers/Serialization.cs
|
// ################################################################################
// Part of PowerShell module : GenXdev.Helpers // Original cmdlet filename : Serialization.cs // Original author : René Vaessen / GenXdev // Version : 2.0.2025 // ################################################################################ // Copyright (c) René Vaessen / GenXdev // // 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 Newtonsoft.Json; using System.Runtime.Serialization.Json; using System.Text; namespace GenXdev.Helpers { /// <summary> /// Provides static utility methods for JSON serialization and deserialization, /// including support for removing comments from JSON strings and writing JSON /// to files. This class uses Newtonsoft.Json for primary serialization and /// DataContractJsonSerializer as a fallback for certain operations. /// </summary> public static class Serialization { /// <summary> /// Serializes an object to a JSON string using DataContractJsonSerializer. /// If indent is true, uses Newtonsoft.Json with indented formatting. /// </summary> /// <param name="obj">The object to serialize.</param> /// <param name="indent">If true, formats the JSON with indentation.</param> /// <returns>A JSON string representation of the object.</returns> public static String ToJson(object obj, bool indent = false) { if (indent) { return JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented); } var serializer = new DataContractJsonSerializer(obj.GetType()); var ms = new MemoryStream(); serializer.WriteObject(ms, obj); ms.Flush(); ms.Position = 0; using (StreamReader sr = new StreamReader(ms, new UTF8Encoding())) { return sr.ReadToEnd(); } } /// <summary> /// Serializes an object to a JSON file. Prepares the target file path forcibly /// and writes the JSON content. Returns true if successful, false otherwise. /// </summary> /// <param name="obj">The object to serialize.</param> /// <param name="filePath">The file path where the JSON will be written.</param> /// <param name="indent">If true, formats the JSON with indentation.</param> /// <returns>True if the file was written successfully, false otherwise.</returns> public static bool ToJsonFile(object obj, string filePath, bool indent = false) { try { FileSystem.ForciblyPrepareTargetFilePath(filePath); using (var stream = new FileStream( filePath, FileMode.Create, FileAccess.Write, FileShare.None, 1024 * 32, FileOptions.None)) { var bytes = new UTF8Encoding(false).GetBytes(ToJson(obj, indent)); stream.Write(bytes, 0, bytes.Length); return true; } } catch { return false; } } /// <summary> /// Serializes an object to a JSON string without type information. /// Uses the same implementation as ToJson. /// </summary> /// <param name="obj">The object to serialize.</param> /// <returns>A JSON string representation of the object.</returns> public static String ToJsonAnonymous(object obj) { return ToJson(obj); } /// <summary> /// Serializes an object to a JSON file without type information. /// Writes the JSON content to the specified file path. /// </summary> /// <param name="obj">The object to serialize.</param> /// <param name="filePath">The file path where the JSON will be written.</param> /// <returns>True if the file was written successfully, false otherwise.</returns> public static bool ToJsonAnonymous(object obj, string filePath) { try { using (var stream = new FileStream( filePath, FileMode.Create, FileAccess.Write, FileShare.None, 1024 * 32, FileOptions.None)) { var bytes = new UTF8Encoding(false).GetBytes(ToJsonAnonymous(obj)); stream.Write(bytes, 0, bytes.Length); return true; } } catch { return false; } } /// <summary> /// Removes comments from an array of JSON lines and returns the cleaned JSON string. /// </summary> /// <param name="jsonLines">An array of JSON strings, each representing a line.</param> /// <returns>A JSON string with comments removed.</returns> public static string RemoveJSONComments(string[] jsonLines) { return RemoveJSONComments(string.Join("\r\n", jsonLines)); } /// <summary> /// Deserializes a JSON string to an object of type T using Newtonsoft.Json. /// </summary> /// <typeparam name="T">The type to deserialize to.</typeparam> /// <param name="JSON">The JSON string to deserialize.</param> /// <returns>An object of type T.</returns> public static T FromJson<T>(String JSON) { return JsonConvert.DeserializeObject<T>(JSON); //var serializer = new DataContractJsonSerializer(typeof(T)); //MemoryStream ms = new MemoryStream(); //using (StreamWriter sw = new StreamWriter(ms, new UTF8Encoding())) //{ // sw.Write(RemoveJSONComments(JSON)); // sw.Flush(); // ms.Flush(); // ms.Position = 0; // return (T)serializer.ReadObject(ms); //} } /// <summary> /// Removes comments from a JSON string. Supports both single-line (//) and /// multi-line (/* */) comments, while preserving content inside string literals. /// </summary> /// <param name="JSON">The JSON string with potential comments.</param> /// <returns>A JSON string with comments removed.</returns> public static string RemoveJSONComments(String JSON) { if (string.IsNullOrEmpty(JSON)) return JSON; var result = new StringBuilder(); bool inString = false; bool inSingleLineComment = false; bool inMultiLineComment = false; char stringChar = '"'; // Iterate through each character in the JSON string to process comments // while preserving string literals for (int i = 0; i < JSON.Length; i++) { char current = JSON[i]; char next = (i + 1 < JSON.Length) ? JSON[i + 1] : '\0'; // Handle string literals - don't process comments inside strings if (!inSingleLineComment && !inMultiLineComment) { if (!inString && (current == '"' || current == '\'')) { inString = true; stringChar = current; result.Append(current); continue; } else if (inString && current == stringChar) { // Check for escaped quotes by counting preceding backslashes bool escaped = false; int backslashCount = 0; for (int j = i - 1; j >= 0 && JSON[j] == '\\'; j--) { backslashCount++; } escaped = backslashCount % 2 == 1; if (!escaped) { inString = false; } result.Append(current); continue; } else if (inString) { result.Append(current); continue; } } // Handle comments if (!inString) { // Start of single-line comment if (!inMultiLineComment && current == '/' && next == '/') { inSingleLineComment = true; i++; // Skip the second '/' continue; } // Start of multi-line comment if (!inSingleLineComment && current == '/' && next == '*') { inMultiLineComment = true; i++; // Skip the '*' continue; } // End of multi-line comment if (inMultiLineComment && current == '*' && next == '/') { inMultiLineComment = false; i++; // Skip the '/' continue; } // End of single-line comment (newline) if (inSingleLineComment && (current == '\r' || current == '\n')) { inSingleLineComment = false; result.Append(current); continue; } // Skip characters inside comments if (inSingleLineComment || inMultiLineComment) { continue; } } // Add non-comment characters result.Append(current); } return result.ToString(); } } } |