using adas_core.Domain.Models.MongoModels; using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using Newtonsoft.Json.Linq; namespace adas_core.Domain.Models.BsonConverters; /// /// A BSON converter responsible for serializing and deserializing /// instances with string keys and object values, /// extending the base serializer infrastructure to support BSON format conversion. /// /// /// This type specializes the generic base serializer for the specific dictionary shape /// Dictionary<string, object>, providing a focused implementation for /// BSON-based persistence and data interchange scenarios. /// public class DictionaryBsonConverter : SerializerBase> { /// /// Deserializes a BSON document into a dictionary where each top-level field name is mapped to its corresponding value. /// Recognized keys (STEPS, BEACONS, CAMERAS, RELAY) are deserialized into their strongly-typed collections or objects, while unknown keys are deserialized based on the current BSON type (numeric values, strings, booleans, arrays of mixed primitive types, or generic BSON documents). /// If the current BSON value is not a document, an empty dictionary is returned. /// /// The BSON deserialization context providing the reader used to traverse the BSON payload. /// The BSON deserialization arguments carrying additional configuration for the deserialization process. /// A dictionary containing the deserialized key/value pairs extracted from the BSON document. public override Dictionary Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var reader = context.Reader; var stepsDictionary = new Dictionary(); if (reader.CurrentBsonType == BsonType.Document) { reader.ReadStartDocument(); while (reader.ReadBsonType() != BsonType.EndOfDocument) { var key = reader.ReadName(); switch (key.ToUpper()) { case "STEPS": var stepList = BsonSerializer.Deserialize>(reader); stepsDictionary[key] = stepList; break; case "BEACONS": var beaconList = BsonSerializer.Deserialize>(reader); stepsDictionary[key] = beaconList; break; case "CAMERAS": var cameraList = BsonSerializer.Deserialize>(reader); stepsDictionary[key] = cameraList; break; case "RELAY": var relayList = BsonSerializer.Deserialize(reader); stepsDictionary[key] = relayList; break; default: switch (reader.CurrentBsonType) { case BsonType.Int32: case BsonType.Int64: stepsDictionary[key] = BsonSerializer.Deserialize(reader); break; case BsonType.Double: stepsDictionary[key] = BsonSerializer.Deserialize(reader); break; case BsonType.String: stepsDictionary[key] = BsonSerializer.Deserialize(reader); break; case BsonType.Boolean: stepsDictionary[key] = BsonSerializer.Deserialize(reader); break; case BsonType.Array: reader.ReadStartArray(); var arrayItems = new List(); while (reader.ReadBsonType() != BsonType.EndOfDocument) switch (reader.CurrentBsonType) { case BsonType.String: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; case BsonType.Int32: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; case BsonType.Int64: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; case BsonType.Boolean: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; case BsonType.Double: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; case BsonType.ObjectId: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; case BsonType.DateTime: arrayItems.Add(BsonSerializer.Deserialize(reader)); break; default: reader.SkipValue(); break; } reader.ReadEndArray(); stepsDictionary[key] = arrayItems; break; default: var bsonDoc = BsonSerializer.Deserialize(reader); stepsDictionary[key] = bsonDoc.ToDictionary(); break; } break; } } reader.ReadEndDocument(); } return stepsDictionary; } /// /// Serializes a dictionary of string keys and object values into a BSON document. When a value's /// type cannot be handled by the underlying key/value serializer, the corresponding key is written /// with a null value as a fallback to preserve all dictionary entries. /// /// The BSON serialization context that provides the writer used to emit the document. /// The BSON serialization arguments associated with the current operation. /// The dictionary whose entries are written as fields of the BSON document. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Dictionary value) { var writer = context.Writer; writer.WriteStartDocument(); foreach (var item in value) { if (TrySerializeKeyValuePair(item, writer)) continue; // Si llegamos a este punto, encontramos un tipo no soportado. writer.WriteName(item.Key); writer.WriteNull(); // Escribe un valor null si el tipo no es soportado. } writer.WriteEndDocument(); } /// /// Attempts to serialize a key-value pair to BSON format using the provided writer. Handles specific well-known keys (steps, beacons, cameras, relay, and uiFontProperties) with their strongly-typed models, and falls back to serializing nested dictionaries, JArrays, or primitive BSON values for any other key. Returns false when the value cannot be converted to a BSON value or when no matching case applies. /// /// The key-value pair to serialize, where the key determines the serialization strategy and the value holds the data to write. /// The BSON writer used to emit the serialized output for the key-value pair. /// true if the key-value pair was successfully serialized; otherwise, false. /// Thrown when a BSON array contains an element whose BSON type is not supported. /// Thrown when the default branch encounters a BSON value type that is not supported. private static bool TrySerializeKeyValuePair(KeyValuePair item, IBsonWriter writer) { writer.WriteName(item.Key); switch (item.Key) { case "steps": if (item.Value is JArray jSteps) { var steps = jSteps.ToObject>(); BsonSerializer.Serialize(writer, typeof(List), steps); return true; } break; case "beacons": if (item.Value is JArray jBeaconConfigs) { var beaconConfigs = jBeaconConfigs.ToObject>(); BsonSerializer.Serialize(writer, typeof(List), beaconConfigs); return true; } break; case "cameras": if (item.Value is JArray jCamerasConfigs) { var cameraConfigs = jCamerasConfigs.ToObject>(); BsonSerializer.Serialize(writer, typeof(List), cameraConfigs); return true; } break; case "relay": if (item.Value is JObject jRelayConfigs) { var relayConfigs = jRelayConfigs.ToObject(); BsonSerializer.Serialize(writer, relayConfigs); return true; } break; case "uiFontProperties": { if (item.Value is JObject jUiFontData) { var uiFontData = jUiFontData.ToObject(); BsonSerializer.Serialize(writer, uiFontData); return true; } } break; default: if (item.Value is Dictionary nestedDictionary) { var bsonDoc = new BsonDocument(nestedDictionary); BsonSerializer.Serialize(writer, bsonDoc); return true; } if (item.Value is JArray jArray) { var listObjects = jArray.ToObject>(); BsonSerializer.Serialize(writer, listObjects); return true; } // Para otros tipos primitivos, utilizamos BsonValue.Create try { var bsonValue = BsonValue.Create(item.Value); switch (bsonValue.BsonType) { case BsonType.String: writer.WriteString(bsonValue.AsString); break; case BsonType.Int32: writer.WriteInt32(bsonValue.AsInt32); break; case BsonType.Int64: writer.WriteInt64(bsonValue.AsInt64); break; case BsonType.Double: writer.WriteDouble(bsonValue.AsDouble); break; case BsonType.Boolean: writer.WriteBoolean(bsonValue.AsBoolean); break; case BsonType.Array: writer.WriteStartArray(); foreach (var arrayValue in bsonValue.AsBsonArray) switch (arrayValue.BsonType) { case BsonType.String: writer.WriteString(arrayValue.AsString); break; case BsonType.Int32: writer.WriteInt32(arrayValue.AsInt32); break; case BsonType.Int64: writer.WriteInt64(arrayValue.AsInt64); break; case BsonType.Double: writer.WriteDouble(arrayValue.AsDouble); break; case BsonType.Boolean: writer.WriteBoolean(arrayValue.AsBoolean); break; default: throw new BsonSerializationException( $"No se puede serializar el tipo Bson en el array: {arrayValue.BsonType}"); } writer.WriteEndArray(); break; default: throw new NotSupportedException($"BsonType {bsonValue.BsonType} no es compatible."); } return true; } catch (ArgumentException) { return false; } } return false; } }