using audit.Model; namespace audit_logs.Utils; using System; using System.Collections.Generic; using MongoDB.Bson; using Newtonsoft.Json.Linq; using Utils; public class JsonComparer { public List GetDifferences(object? originalJson, object? modifiedJson) { var changes = new List(); // Convertir los objetos a cadenas JSON; usar "{}" si el objeto es null var originalJsonString = originalJson != null ? JsonConverter.ConvertObjectToJson(originalJson) : "{}"; var modifiedJsonString = modifiedJson != null ? JsonConverter.ConvertObjectToJson(modifiedJson) : "{}"; // Intentar parsear a JObject solo si el JSON es un objeto var originalObj = ParseAsJObject(originalJsonString); var modifiedObj = ParseAsJObject(modifiedJsonString); //eliminamos aquellas claves que contengan null, por ejemplo "allergies": [ // { // "optionType": null, // "name": "Sí", // "iconDefault": "icCheck", // "iconLight": null, // "iconDark": null, // "color": null, // "bgColor": null, // "description": null, // "initDate": null, // "endDate": null // } // ] if (originalObj != null) { originalObj = JsonClean.CleanJObject(originalObj); } if (modifiedObj != null) { modifiedObj = JsonClean.CleanJObject(modifiedObj); } // Comparar los objetos JSON CompareJTokens(originalObj, modifiedObj, changes, string.Empty); return changes; } /// /// Intenta parsear una cadena JSON como JObject. Devuelve null si no es un objeto. /// /// La cadena JSON a validar. /// Un JObject si es válido, de lo contrario null. private static JObject? ParseAsJObject(string json) { try { var token = JToken.Parse(json); return token as JObject; // Devuelve null si no es un JObject } catch { return null; // Si ocurre un error, no es un JSON válido } } private void CompareJTokens(JToken original, JToken modified, List changes, string path) { foreach (var property in modified.Children()) { var fieldPath = string.IsNullOrEmpty(path) ? property.Name : $"{path}.{property.Name}"; var originalValue = original[property.Name]; var modifiedValue = property.Value; // Ignorar campos donde ambos valores son null if (originalValue == null && modifiedValue.Type == JTokenType.Null) { continue; } if (originalValue == null) { // Campo nuevo changes.Add(new AuditRecord.Change { Field = fieldPath, OldValue = BsonNull.Value, NewValue = ConvertToBsonValue(modifiedValue), ValueType = GetBsonType(modifiedValue) }); } else if (!JToken.DeepEquals(originalValue, modifiedValue)) { if (originalValue.Type == JTokenType.Object && modifiedValue.Type == JTokenType.Object) { // Comparar objetos anidados CompareJTokens(originalValue, modifiedValue, changes, fieldPath); } else { // Campo modificado changes.Add(new AuditRecord.Change { Field = fieldPath, OldValue = ConvertToBsonValue(originalValue), NewValue = ConvertToBsonValue(modifiedValue), ValueType = GetBsonType(modifiedValue) }); } } } // Campos eliminados foreach (var property in original.Children()) { var fieldPath = string.IsNullOrEmpty(path) ? property.Name : $"{path}.{property.Name}"; if (modified[property.Name] == null) { changes.Add(new AuditRecord.Change { Field = fieldPath, OldValue = ConvertToBsonValue(property.Value), NewValue = BsonNull.Value, ValueType = GetBsonType(property.Value) }); } } } /*private BsonValue ConvertToBsonValue(JToken token) { return token.Type switch { JTokenType.String => new BsonString(token.ToString()), JTokenType.Integer => new BsonInt32(token.ToObject()), JTokenType.Float => new BsonDouble(token.ToObject()), JTokenType.Boolean => new BsonBoolean(token.ToObject()), JTokenType.Date => new BsonDateTime(token.ToObject()), JTokenType.Null => BsonNull.Value, JTokenType.Object => BsonDocument.Parse(token.ToString()), JTokenType.Array => new BsonArray(token.ToObject>()), _ => new BsonString(token.ToString()) }; }*/ private BsonValue ConvertToBsonValue(JToken token) { switch (token.Type) { case JTokenType.String: return new BsonString(token.ToString()); case JTokenType.Integer: return BsonValue.Create(token.ToObject()); case JTokenType.Float: return BsonValue.Create(token.ToObject()); case JTokenType.Boolean: return new BsonBoolean(token.ToObject()); case JTokenType.Date: return new BsonDateTime(token.ToObject()); case JTokenType.Null: return BsonNull.Value; case JTokenType.Object: return BsonDocument.Parse(token.ToString()); case JTokenType.Array: var array = new BsonArray(); foreach (var item in token.Children()) { array.Add(ConvertToBsonValue(item)); } return array; default: return new BsonString(token.ToString()); // Trata cualquier otro tipo como string } } private string GetBsonType(JToken token) { return token.Type switch { JTokenType.String => "string", JTokenType.Integer => "int", JTokenType.Float => "double", JTokenType.Boolean => "bool", JTokenType.Date => "date", JTokenType.Null => "null", JTokenType.Object => "object", JTokenType.Array => "array", _ => "unknown" }; } }