使用 System.Text.Json 将 IConfiguration 序列化回 Json

Itd*_*Itd 6 .net c# configuration .net-core system.text.json

我将一些 IConfiguration 作为 json 存储在我的 sqlserver 数据库中,这样我就可以将它们绑定到一些已经构造的类以提供动态设置。

在某些时候,我可能会在运行时更改新的绑定属性,然后更新数据库。问题是,当我需要时,该类可能具有更多不应绑定且不应序列化的属性。因此,我将 IConfiguration 保留为我的类的属性。我使用这种方法的另一个原因是我需要从已加载配置的类中实例化其他子类,并在执行时将它们保存到数据库中。

问题是,当我序列化 IConfiguration 时,我只得到一个空的 json 字符串,如“{}”。我想我可以利用 .AsEnumerable() 做一些恶作剧,但没有更好的方法吗?

我的示例代码看起来有点像这样

public class ConfigurableClass
{

    public int ChildrenCount { get; set; } = 1069;
    public bool IsFast { get; set; } = false;
    public bool HasChildren { get; set; } = false;

    public int Id { get; }

    public ConfigurableClass(int id) { Id = id; }
}

static void Main(string[] args)
{

    IEnumerable<string> configs = SqlConfigLoader.LoadConfig();

    foreach (var str in configs)
    {
        Console.WriteLine("Parsing new Config:");

        var builder = new ConfigurationBuilder();

        IConfiguration cfg = builder.AddJsonStream(new MemoryStream(Encoding.Default.GetBytes(str)))
                .Build();

        var stepExample = new ConfigurableClass(9);

        cfg.Bind(stepExample);

        //do work with the class that might change the value of binded properties                   

        var updatedCfg = cfg;

        Console.WriteLine(JsonSerializer.Serialize(updatedCfg));

        Console.WriteLine();
    }

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

编辑

我还尝试了一种不同的方法,将 IConfiguration 转换为像这样的嵌套字典

ublic static class IConfigurationExtensions
{
   public static Dictionary<string,object> ToNestedDicionary(this IConfiguration configuration)
   {
       var result = new Dictionary<string, object>();
       var children = configuration.GetChildren();
       if (children.Any()) 
           foreach (var child in children)
               result.Add(child.Key, child.ToNestedDicionary());
       else 
           if(configuration is IConfigurationSection section)
               result.Add(section.Key, section.Get(typeof(object)));

       return result;
   }        
}
Run Code Online (Sandbox Code Playgroud)

但我丢失了给定 JsonElement 背后的隐式类型:

如果我序列化生成的字典,我会得到类似“Property”:“True”而不是“Property”:true

pin*_*x33 15

首先,为什么

尝试序列化IConfiguration这种方式序列化不会按照您想要的方式工作。让我们探究一下原因。

序列化接口

您没有获得任何属性的部分原因是因为泛型类型参数SerializeIConfiguration。换句话说,您正在调用:

JsonSerializer.Serialize<IConfiguration>(updatedCfg)
Run Code Online (Sandbox Code Playgroud)

当 System.Text.Json 使用通用参数进行序列化时,它仅(默认情况下没有任何自定义转换器)序列化该接口的公共属性。在这种情况下IConfiguration没有公共属性(索引器除外),因此您的输出是空的 json。

使用运行时类型信息

现在,通常为了解决这个问题,您将使用非泛型重载并传递类型。例如,看起来像:

JsonSerializer.Serialize(updatedCfg, updatedCfg.GetType());
Run Code Online (Sandbox Code Playgroud)

或者也可以使用object作为类型参数:

JsonSerializer.Serialize<object>(updatedCfg);
Run Code Online (Sandbox Code Playgroud)

然后 System.Text.Json 将使用运行时类型信息来确定要序列化的属性。

ConfigurationRoot

现在问题的第二部分是,不幸的是,由于配置系统的设计方式,这仍然无法正常工作。班级ConfigurationRoot(结果Build)可以聚合许多配置源。数据单独存储在每个提供商内部(甚至外部)。当您从配置中请求一个值时,它会循环遍历每个提供程序以找到匹配项。

所有这些都表明IConfiguration对象的具体/运行时类型仍然不具有您想要序列化的公共属性。事实上,在这种情况下传递运行时类型会比模仿接口的行为更糟​​糕,因为它将尝试序列化该类型的唯一公共属性(ConfigurationRoot.Providers)。这将为您提供序列化提供程序的列表,每个提供程序的类型为IConfigurationProvider并且具有零个公共属性。

一个潜在的解决方案

由于您尝试序列化最终绑定到对象的配置,因此解决方法是重新序列化对象:

var stepExample = new ConfigurableClass(9);
cfg.Bind(stepExample);
var json1 = JsonSerializer.Serialize(stepExample, stepExample.GetType());
// or with the generic version which will work here
var json2 = JsonSerializer.Serialize(stepExample);
Run Code Online (Sandbox Code Playgroud)

替代解决方案 -AsEnumerable

IConfiguration最终是键值对的集合。我们可以使用AsEnumerable扩展方法来创建List<KeyValuePair<string, string>>整个配置。稍后可以将其反序列化并传递给类似的东西 AddInMemoryCollection

您将需要Microsoft.Extensions.Configuration.Abstractions 包(可能已经被传递引用)和以下using指令:

using Microsoft.Extensions.Configuration;
Run Code Online (Sandbox Code Playgroud)

然后您可以创建所有值的列表(带有Section:Key格式的键)

var configAsList = cfg.AsEnumerable().ToList();
var json = JsonSerializer.Serialize(configAsList);
Run Code Online (Sandbox Code Playgroud)

或者您可以使用ToDictionary并序列化它。

var configAsDict = cfg.AsEnumerable().ToDictionary(c => c.Key, c => c.Value);
var json = JsonSerializer.Serialize(configAsDict);
Run Code Online (Sandbox Code Playgroud)

两种格式都可以使用AddInMemoryCollection,因为只需要一个IEnumerable<KeyValuePair<string, string>>(两种类型都是)。但是,如果您想使用,您可能需要字典格式,因为我认为AddJsonFile/Stream它们不支持键/值对数组。

字符串,字符串,除了字符串什么都没有

您似乎有这样的印象:IConfiguration对象存储与 JSON 元素类型相对应的ints、bools 等(例如)。这是不正确的。an 中的所有数据都以字符串形式IConfiguration存储。基本配置提供程序类都需要填充数据。甚至 JSON 配置提供程序也会对值执行显式操作。IDictionary<string, string>ToString

当您调用,或 时,字符串类型值将转换为强类型值。这些使用配置绑定器,而配置绑定器又使用注册的且众所周知的字符串解析方法。但在幕后一切仍然是一根绳子。这意味着您的 json 文件是否具有带有 value 的字符串属性或带有 value 的布尔属性并不重要。当映射到属性时,绑定器将适当地转换该值。BindGet<>GetValue<>TypeConverters"True"trueboolean

使用上述字典序列化方法将按预期工作。