使用 System.Text.Json 将 IConfiguration 序列化回 Json [英] Using System.Text.Json to Serialize an IConfiguration back to Json

查看:57
本文介绍了使用 System.Text.Json 将 IConfiguration 序列化回 Json的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm storing some IConfiguration as json in my sqlserver db so I can then bind them to some already constructed classes in order to provide dynamic settings.

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

At some point I might change the binded properties new at runtime and then update the db. The thing is that when i need to, the class might have more properties that aren't supposed to be bound and shouln't be serialized. I am therefore keeping the IConfiguration as a property of my class. Another reason why I'm using this approach is that I need to istantiate other children classes from the class that has loaded the configs, and save them to db when i do.

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

The thing is that when I serialize an IConfiguration i only get an empty json string like "{}". I suppose i could do some shenanigans leveraging .AsEnumerable() but isn't there a better way?

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

My sample code would look somewhat like this

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();
}

编辑

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

I Also tried a diffent approach, by converting the IConfiguration to a nested dictionary like this

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;
   }        
}

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

But I lose the implicit type behind a given JsonElement:

如果我序列化结果字典,我会得到类似Property":True"的信息.而不是财产": 真的

if i serialize the resulting dictionary i get thing like "Property": "True" instead of "Property" : true

推荐答案

一、为什么

尝试以这种方式序列化 IConfiguration 不会像您希望的那样工作.让我们来探讨一下原因.

First, the why

Attempting to serialize the IConfiguration this way is not going to work how you want it to. Let's explore why.

部分没有属性的原因是Serialize的泛型类型参数是IConfiguration.换句话说,您正在调用:

Part of the reason you get no properties is because the generic type argument to Serialize is IConfiguration. In other words you are calling:

JsonSerializer.Serialize<IConfiguration>(updatedCfg)

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

When System.Text.Json serializes using a generic parameter it only (by default without any custom converters) serializes the public properties of that interface. In this case IConfiguration has no public properties (other than an indexer) so your output is empty json.

现在,一般来说要解决这个问题,您可以使用 非通用重载 并传递类型.例如,看起来像:

Now, in general to get around this you would use the non-generic overload and pass the type. For example that would look like:

JsonSerializer.Serialize(updatedCfg, updatedCfg.GetType());

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

Or alternatively by using object as the type parameter:

JsonSerializer.Serialize<object>(updatedCfg);

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

System.Text.Json will then use the runtime type information in order to determine what properties to serialize.

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

Now the second part of your problem is that this is unfortunately still not going to work due to how the configuration system is designed. The ConfigurationRoot class (the result of Build) can aggregate many configuration sources. The data is stored individually within (or even external to) each provider. When you request a value from the configuration it loops through each provider in order to locate a match.

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

All of this to say that the concrete/runtime type of your IConfiguration object will still not have the public properties you desire to serialize. In fact, passing the runtime type in this case will do worse than mimic the behavior of the interface as it will attempt to serialize the only public property of that type (ConfigurationRoot.Providers). This will give you a list of serialized providers, each typed as IConfigurationProvider and having zero public properties.

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

Since you are attempt to serialize the configuration that you are ultimately binding to an object, a workaround would be to re-serialize that object instead:

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);

另一种解决方案 - AsEnumerable

IConfiguration 最终是键值对的集合.我们可以利用 AsEnumerable 扩展方法,用于从整个配置中创建 List>.这可以稍后反序列化并传递给类似 AddInMemoryCollection

An alternative solution - AsEnumerable

IConfiguration is ultimately a collection of key value pairs. We can make use of the AsEnumerable extension method to create a List<KeyValuePair<string, string>> out of the entire configuration. This can later be deserialized and passed to something like AddInMemoryCollection

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

You'll need the Microsoft.Extensions.Configuration.Abstractions package (which is likely already transitively referenced) and the following using directive:

using Microsoft.Extensions.Configuration;

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

And then you can create a list of all the values (with keys in Section:Key format)

var configAsList = cfg.AsEnumerable().ToList();
var json = JsonSerializer.Serialize(configAsList);

或者您可以使用 ToDictionary 并对其进行序列化.

Or you can use ToDictionary and serialize that instead.

var configAsDict = cfg.AsEnumerable().ToDictionary(c => c.Key, c => c.Value);
var json = JsonSerializer.Serialize(configAsDict);

两种格式都适用于 AddInMemoryCollection,因为它只需要一个 IEnumerable>(这两种类型都是).但是,如果您希望使用 AddJsonFile/Stream,您可能需要 Dictionary 格式,因为我不认为那些支持键/值对数组.

Both formats will work with AddInMemoryCollection as that only requires an IEnumerable<KeyValuePair<string, string>> (which both types are). However, you will likely need the Dictionary format if you wish to use AddJsonFile/Stream as I don't think those support an array of key/value pairs.

您似乎认为 IConfiguration 对象正在存储 ints、bools 等(例如)对应于JSON 元素类型.这是不正确的.IConfiguration 中的所有数据都以字符串化形式 存储.基本配置提供程序类都期望 IDictionary 填充数据.即使是 JSON 配置提供程序 对值执行显式ToString.

You seem to be under the impression that IConfiguration objects are storing ints, bools, etc. (for example) corresponding to the JSON Element type. This is incorrect. All data within an IConfiguration is stored in stringified form. The base Configuration Provider classes all expect an IDictionary<string, string> filled with data. Even the JSON Configuration Providers perform an explicit ToString on the values.

当您调用 BindGet<>GetValue<>.这些使用配置 binder,后者又使用注册的 TypeConverters 和众所周知的字符串解析方法.但在封面下,一切仍然是一个字符串.这意味着您的 json 文件是否具有值为 "True" 的字符串属性或值为 true 的布尔属性并不重要.当映射到 boolean 属性时,活页夹将适当地转换该值.

The stringyly-typed values are turned into strongly-typed ones when you call Bind, Get<> or GetValue<>. These make use of the configuration binder which in turn uses registered TypeConverters and well know string parsing methods. But under the covers everything is still a string. This means it doesn't matter if your json file has a string property with value "True" or a boolean property with value true. The binder will appropriately convert the value when mapping to a boolean property.

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

Using the above dictionary serializing method will work as intended.

这篇关于使用 System.Text.Json 将 IConfiguration 序列化回 Json的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆