using System.Collections.Immutable;

namespace Dpz.Core.Service.OpenIddictStores;

internal static class OpenIddictBsonPropertySerializer
{
    private static readonly JsonSerializerOptions SerializerOptions = new(
        JsonSerializerDefaults.Web
    );

    public static ImmutableDictionary<string, JsonElement> ToProperties(BsonDocument? document)
    {
        if (document is null || document.ElementCount == 0)
        {
            return ImmutableDictionary<string, JsonElement>.Empty;
        }

        var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>(
            StringComparer.Ordinal,
            JsonElementValueComparer.Instance
        );

        foreach (var element in document.Elements)
        {
            builder[element.Name] = ToJsonElement(element.Value);
        }

        return builder.ToImmutable();
    }

    public static BsonDocument? ToDocument(ImmutableDictionary<string, JsonElement> properties)
    {
        if (properties.Count == 0)
        {
            return null;
        }

        var document = new BsonDocument();
        foreach (var (key, value) in properties)
        {
            document[key] = ToBsonValue(value);
        }

        return document;
    }

    private static JsonElement ToJsonElement(BsonValue value)
    {
        using var document = JsonDocument.Parse(
            JsonSerializer.Serialize(ToClrValue(value), SerializerOptions)
        );
        return document.RootElement.Clone();
    }

    private static object? ToClrValue(BsonValue value)
    {
        if (value.IsBsonNull || value.IsBsonUndefined)
        {
            return null;
        }

        if (value.IsBsonDocument)
        {
            return value.AsBsonDocument.Elements.ToDictionary(
                element => element.Name,
                element => ToClrValue(element.Value),
                StringComparer.Ordinal
            );
        }

        if (value.IsBsonArray)
        {
            return value.AsBsonArray.Select(ToClrValue).ToArray();
        }

        if (value.IsBoolean)
        {
            return value.AsBoolean;
        }

        if (value.IsString)
        {
            return value.AsString;
        }

        if (value.IsInt32)
        {
            return value.AsInt32;
        }

        if (value.IsInt64)
        {
            return value.AsInt64;
        }

        if (value.IsDouble)
        {
            return value.AsDouble;
        }

        if (value.IsDecimal128)
        {
            return Decimal128.ToDecimal(value.AsDecimal128);
        }

        if (value.IsValidDateTime)
        {
            return value.ToUniversalTime();
        }

        if (value.IsObjectId)
        {
            return value.AsObjectId.ToString();
        }

        return value.ToString();
    }

    private static BsonValue ToBsonValue(JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.Object => ToBsonDocument(element),
            JsonValueKind.Array => ToBsonArray(element),
            JsonValueKind.String => new BsonString(element.GetString()),
            JsonValueKind.Number => ToBsonNumber(element),
            JsonValueKind.True => BsonBoolean.True,
            JsonValueKind.False => BsonBoolean.False,
            _ => BsonNull.Value,
        };
    }

    private static BsonDocument ToBsonDocument(JsonElement element)
    {
        var document = new BsonDocument();
        foreach (var property in element.EnumerateObject())
        {
            document[property.Name] = ToBsonValue(property.Value);
        }

        return document;
    }

    private static BsonArray ToBsonArray(JsonElement element)
    {
        var array = new BsonArray();
        foreach (var item in element.EnumerateArray())
        {
            array.Add(ToBsonValue(item));
        }

        return array;
    }

    private static BsonValue ToBsonNumber(JsonElement element)
    {
        if (element.TryGetInt32(out var int32))
        {
            return new BsonInt32(int32);
        }

        if (element.TryGetInt64(out var int64))
        {
            return new BsonInt64(int64);
        }

        if (element.TryGetDecimal(out var decimalValue))
        {
            return new BsonDecimal128(decimalValue);
        }

        return new BsonDouble(element.GetDouble());
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个用于在 BSON 格式(MongoDB 使用的二进制 JSON 格式)和 System.Text.Json 的 JsonElement 之间进行双向转换的静态工具类,主要用于 OpenIddict 存储层的数据序列化。

核心功能

1. ToProperties 方法

public static ImmutableDictionary<string, JsonElement> ToProperties(BsonDocument? document)
  • 功能:将 BSON 文档转换为不可变字典(键为字符串,值为 JsonElement)
  • 处理逻辑
    • 如果文档为空或没有元素,返回空字典
    • 遍历 BSON 文档的每个元素,将其转换为 JsonElement
    • 使用顺序字符串比较器和自定义的 JsonElement 比较器

2. ToDocument 方法

public static BsonDocument? ToDocument(ImmutableDictionary<string, JsonElement> properties)
  • 功能:将不可变字典转换回 BSON 文档
  • 处理逻辑
    • 如果字典为空,返回 null
    • 遍历字典的每个键值对,将 JsonElement 转换为 BsonValue

3. ToJsonElement 方法(私有)

private static JsonElement ToJsonElement(BsonValue value)
  • 功能:将单个 BSON 值转换为 JsonElement
  • 转换策略
    1. 先将 BsonValue 转换为 CLR 对象(通过 ToClrValue
    2. 使用 JsonSerializer 序列化为 JSON 字符串
    3. 解析为 JsonDocument 并克隆根元素

4. ToClrValue 方法(私有)

private static object? ToClrValue(BsonValue value)
  • 功能:将 BSON 值转换为对应的 CLR(公共语言运行时)类型
  • 支持的类型映射
    • BsonNull/BsonUndefinednull
    • BsonDocumentDictionary<string, object?>(递归处理)
    • BsonArrayobject[](递归处理)
    • Booleanbool
    • Stringstring
    • Int32int
    • Int64long
    • Doubledouble
    • Decimal128decimal
    • DateTime → UTC 时间
    • ObjectIdstring(转为字符串表示)
    • 其他 → 调用 ToString()

5. ToBsonValue 方法(私有)

private static BsonValue ToBsonValue(JsonElement element)
  • 功能:将 JsonElement 转换为 BSON 值
  • 使用模式匹配处理不同类型
    • Object → BsonDocument
    • Array → BsonArray
    • String → BsonString
    • Number → 根据精度转换(Int32/Int64/Decimal128/Double)
    • True/False → BsonBoolean
    • 其他 → BsonNull

6. 辅助转换方法

  • ToBsonDocument:递归转换 JSON 对象
  • ToBsonArray:递归转换 JSON 数组
  • ToBsonNumber:智能识别数字类型(优先使用较小类型)

设计亮点

  1. 不可变性:使用 ImmutableDictionary 保证线程安全
  2. 类型安全:完整的类型映射,避免数据丢失
  3. 递归处理:支持嵌套的复杂数据结构
  4. 智能类型推断:数字类型按精度自动选择最合适的存储格式
  5. JSON 标准兼容:使用 Web 默认序列化选项

使用场景

该类主要用于 OpenIddict(OAuth 2.0/OpenID Connect 框架)的 MongoDB 存储实现中,用于存储和检索授权、令牌等配置属性。

评论加载中...