Files
adas-core/adas-core.Domain/Models/BsonConverters/DictionaryBsonConverter.cs
T
2026-06-26 10:29:23 +02:00

307 lines
15 KiB
C#

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;
/// <summary>
/// A BSON converter responsible for serializing and deserializing
/// <see cref="Dictionary{TKey, TValue}"/> instances with string keys and object values,
/// extending the base serializer infrastructure to support BSON format conversion.
/// </summary>
/// <remarks>
/// This type specializes the generic base serializer for the specific dictionary shape
/// <c>Dictionary&lt;string, object&gt;</c>, providing a focused implementation for
/// BSON-based persistence and data interchange scenarios.
/// </remarks>
public class DictionaryBsonConverter : SerializerBase<Dictionary<string, object>>
{
/// <summary>
/// Deserializes a BSON document into a dictionary where each top-level field name is mapped to its corresponding value.
/// Recognized keys (<c>STEPS</c>, <c>BEACONS</c>, <c>CAMERAS</c>, <c>RELAY</c>) 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.
/// </summary>
/// <param name="context">The BSON deserialization context providing the reader used to traverse the BSON payload.</param>
/// <param name="args">The BSON deserialization arguments carrying additional configuration for the deserialization process.</param>
/// <returns>A dictionary containing the deserialized key/value pairs extracted from the BSON document.</returns>
public override Dictionary<string, object> Deserialize(BsonDeserializationContext context,
BsonDeserializationArgs args)
{
var reader = context.Reader;
var stepsDictionary = new Dictionary<string, object>();
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<List<Step>>(reader);
stepsDictionary[key] = stepList;
break;
case "BEACONS":
var beaconList = BsonSerializer.Deserialize<List<LightBeacon>>(reader);
stepsDictionary[key] = beaconList;
break;
case "CAMERAS":
var cameraList = BsonSerializer.Deserialize<List<Camera>>(reader);
stepsDictionary[key] = cameraList;
break;
case "RELAY":
var relayList = BsonSerializer.Deserialize<Relay>(reader);
stepsDictionary[key] = relayList;
break;
default:
switch (reader.CurrentBsonType)
{
case BsonType.Int32:
case BsonType.Int64:
stepsDictionary[key] = BsonSerializer.Deserialize<long>(reader);
break;
case BsonType.Double:
stepsDictionary[key] = BsonSerializer.Deserialize<double>(reader);
break;
case BsonType.String:
stepsDictionary[key] = BsonSerializer.Deserialize<string>(reader);
break;
case BsonType.Boolean:
stepsDictionary[key] = BsonSerializer.Deserialize<bool>(reader);
break;
case BsonType.Array:
reader.ReadStartArray();
var arrayItems = new List<object>();
while (reader.ReadBsonType() != BsonType.EndOfDocument)
switch (reader.CurrentBsonType)
{
case BsonType.String:
arrayItems.Add(BsonSerializer.Deserialize<string>(reader));
break;
case BsonType.Int32:
arrayItems.Add(BsonSerializer.Deserialize<int>(reader));
break;
case BsonType.Int64:
arrayItems.Add(BsonSerializer.Deserialize<long>(reader));
break;
case BsonType.Boolean:
arrayItems.Add(BsonSerializer.Deserialize<bool>(reader));
break;
case BsonType.Double:
arrayItems.Add(BsonSerializer.Deserialize<double>(reader));
break;
case BsonType.ObjectId:
arrayItems.Add(BsonSerializer.Deserialize<ObjectId>(reader));
break;
case BsonType.DateTime:
arrayItems.Add(BsonSerializer.Deserialize<DateTime>(reader));
break;
default:
reader.SkipValue();
break;
}
reader.ReadEndArray();
stepsDictionary[key] = arrayItems;
break;
default:
var bsonDoc = BsonSerializer.Deserialize<BsonDocument>(reader);
stepsDictionary[key] = bsonDoc.ToDictionary();
break;
}
break;
}
}
reader.ReadEndDocument();
}
return stepsDictionary;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="context">The BSON serialization context that provides the writer used to emit the document.</param>
/// <param name="args">The BSON serialization arguments associated with the current operation.</param>
/// <param name="value">The dictionary whose entries are written as fields of the BSON document.</param>
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args,
Dictionary<string, object> 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();
}
/// <summary>
/// Attempts to serialize a key-value pair to BSON format using the provided writer. Handles specific well-known keys (<c>steps</c>, <c>beacons</c>, <c>cameras</c>, <c>relay</c>, and <c>uiFontProperties</c>) with their strongly-typed models, and falls back to serializing nested dictionaries, JArrays, or primitive BSON values for any other key. Returns <c>false</c> when the value cannot be converted to a BSON value or when no matching case applies.
/// </summary>
/// <param name="item">The key-value pair to serialize, where the key determines the serialization strategy and the value holds the data to write.</param>
/// <param name="writer">The BSON writer used to emit the serialized output for the key-value pair.</param>
/// <returns><c>true</c> if the key-value pair was successfully serialized; otherwise, <c>false</c>.</returns>
/// <exception cref="BsonSerializationException">Thrown when a BSON array contains an element whose BSON type is not supported.</exception>
/// <exception cref="NotSupportedException">Thrown when the default branch encounters a BSON value type that is not supported.</exception>
private static bool TrySerializeKeyValuePair(KeyValuePair<string, object> item, IBsonWriter writer)
{
writer.WriteName(item.Key);
switch (item.Key)
{
case "steps":
if (item.Value is JArray jSteps)
{
var steps = jSteps.ToObject<List<Step>>();
BsonSerializer.Serialize(writer, typeof(List<Step>), steps);
return true;
}
break;
case "beacons":
if (item.Value is JArray jBeaconConfigs)
{
var beaconConfigs = jBeaconConfigs.ToObject<List<LightBeacon>>();
BsonSerializer.Serialize(writer, typeof(List<LightBeacon>), beaconConfigs);
return true;
}
break;
case "cameras":
if (item.Value is JArray jCamerasConfigs)
{
var cameraConfigs = jCamerasConfigs.ToObject<List<Camera>>();
BsonSerializer.Serialize(writer, typeof(List<Camera>), cameraConfigs);
return true;
}
break;
case "relay":
if (item.Value is JObject jRelayConfigs)
{
var relayConfigs = jRelayConfigs.ToObject<Relay>();
BsonSerializer.Serialize(writer, relayConfigs);
return true;
}
break;
case "uiFontProperties":
{
if (item.Value is JObject jUiFontData)
{
var uiFontData = jUiFontData.ToObject<UiFontData>();
BsonSerializer.Serialize(writer, uiFontData);
return true;
}
}
break;
default:
if (item.Value is Dictionary<string, object> nestedDictionary)
{
var bsonDoc = new BsonDocument(nestedDictionary);
BsonSerializer.Serialize(writer, bsonDoc);
return true;
}
if (item.Value is JArray jArray)
{
var listObjects = jArray.ToObject<List<object>>();
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;
}
}