使用 System.Text.Json 将 IConfiguration 序列化回 Json
Using System.Text.Json to Serialize an IConfiguration back to Json
我将一些 IConfiguration 作为 json 存储在我的 sqlserver 数据库中,这样我就可以将它们绑定到一些已经构建的 classes 以提供动态设置。
有时我可能会在运行时更改新的绑定属性,然后更新数据库。问题是,当我需要时,class 可能有更多不应绑定且不应序列化的属性。因此,我将 IConfiguration 保留为 class 的 属性。我使用这种方法的另一个原因是我需要从已加载配置的 class 实例化其他子 classes,并在我这样做时将它们保存到数据库。
问题是,当我序列化一个 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();
}
编辑
我还尝试了一种不同的方法,将 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;
}
}
但是我丢失了给定 JsonElement 背后的隐式类型:
如果我序列化生成的字典,我会得到类似“属性”的东西:“True”而不是“属性”:true
首先,原因
尝试以这种方式序列化 IConfiguration
不会按您希望的方式工作。让我们来探究一下原因。
序列化接口
部分 你没有属性的原因是因为 Serialize
的通用类型参数是 IConfiguration
。换句话说,您正在调用:
JsonSerializer.Serialize<IConfiguration>(updatedCfg)
当 System.Text.Json 使用通用参数序列化时,它仅(默认情况下没有任何自定义转换器)序列化该接口的 public 属性。在这种情况下 IConfiguration
没有 public 属性(索引器除外),因此您的输出为空 json.
使用运行时类型信息
现在,一般来说 要解决这个问题,您可以使用 non-generic overload 并传递类型。例如,它看起来像:
JsonSerializer.Serialize(updatedCfg, updatedCfg.GetType());
或者使用 object
作为类型参数:
JsonSerializer.Serialize<object>(updatedCfg);
System.Text.Json 然后将使用 运行时类型 信息来确定要序列化的属性。
ConfigurationRoot
现在你的问题的第二部分是,不幸的是,由于配置系统的设计方式,这仍然无法正常工作。 ConfigurationRoot
class(Build
的结果)可以聚合许多配置源。数据单独存储在每个提供商内部(甚至外部)。当您从配置中请求一个值时,它会循环遍历每个提供程序以找到匹配项。
所有这一切都表明 IConfiguration
对象的 concrete/runtime 类型仍然没有您希望序列化的 public 属性。事实上,在这种情况下传递运行时类型比模仿接口的行为更糟糕,因为它将尝试序列化唯一的 public 属性键入 (ConfigurationRoot.Providers
). This will give you a list of serialized providers, each typed as IConfigurationProvider
并具有零个 public 属性。
一个潜在的解决方案
由于您尝试序列化最终绑定到对象的配置,解决方法是重新序列化 that 对象:
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<KeyValuePair<string, string>>
。稍后可以将其反序列化并传递给 AddInMemoryCollection
您将需要 Microsoft.Extensions.Configuration.Abstractions package(可能已经被传递引用)和以下 using
指令:
using Microsoft.Extensions.Configuration;
然后您可以创建所有值的列表(使用 Section:Key
格式的键)
var configAsList = cfg.AsEnumerable().ToList();
var json = JsonSerializer.Serialize(configAsList);
或者您可以使用 ToDictionary
并将其序列化。
var configAsDict = cfg.AsEnumerable().ToDictionary(c => c.Key, c => c.Value);
var json = JsonSerializer.Serialize(configAsDict);
两种格式都适用于 AddInMemoryCollection
,因为它只需要一个 IEnumerable<KeyValuePair<string, string>>
(这两种类型都是)。但是,如果您希望使用 AddJsonFile/Stream
,您可能需要字典格式,因为我 认为 不支持 key/value 对数组。
字符串,字符串,只有字符串
您的印象似乎是 IConfiguration
对象正在存储 int
s、bool
s 等(例如)对应于 JSON 元素类型。这是不正确的。 IConfiguration
中的所有数据都以 字符串化形式 存储。基本配置提供程序 class 都需要 IDictionary<string, string>
填充数据。甚至 JSON 配置提供程序 perform an explicit ToString
上的值。
stringy-typed 值在调用 Bind
, Get<>
或 GetValue<>
。这些使用配置 binder,后者又使用注册的 TypeConverters
和众所周知的字符串解析方法。但在幕后,一切仍然是一个字符串。这意味着您的 json 文件是否包含值为 "True"
的字符串 属性 或值为 true
的布尔值 属性 并不重要。当映射到 boolean
属性.
时,活页夹将适当地转换值
使用上述字典序列化方法将按预期工作。
只是 运行 进入类似的需求。序列化 IConfiguration
以通过总线发送。这是我想出的
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
namespace A6k
{
public class ConfigurationConverter : JsonConverter<IConfiguration>
{
public override IConfiguration Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var root = new ConfigurationRoot(new List<IConfigurationProvider>(new[] { new MemoryConfigurationProvider(new MemoryConfigurationSource()) }));
var pathParts = new Stack<string>();
string currentProperty = null;
string currentPath = null;
while (reader.Read() && (reader.TokenType != JsonTokenType.EndObject || pathParts.Count > 0))
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
currentProperty = reader.GetString();
break;
case JsonTokenType.String:
if (pathParts.Count == 0)
root[currentProperty] = reader.GetString();
else
root[ConfigurationPath.Combine(currentPath, currentProperty)] = reader.GetString();
break;
case JsonTokenType.StartObject:
pathParts.Push(currentProperty);
currentPath = ConfigurationPath.Combine(pathParts);
break;
case JsonTokenType.EndObject:
pathParts.Pop();
currentPath = ConfigurationPath.Combine(pathParts);
break;
}
}
return root;
}
public override void Write(Utf8JsonWriter writer, IConfiguration value, JsonSerializerOptions options)
{
if (value is IConfigurationSection section)
{
if (section.Value is null)
writer.WriteStartObject(section.Key);
else
{
writer.WriteString(section.Key, section.Value);
return;
}
}
else
writer.WriteStartObject();
foreach (var child in value.GetChildren())
Write(writer, child, options);
writer.WriteEndObject();
}
}
}
它在反序列化时只使用一个内存配置提供程序。但结果 IConfiguration
的行为方式与发送的实例相同。
只需将其作为转换器添加到您的序列化中即可:
var json = JsonSerializer.Serialize(configuration, new JsonSerializerOptions
{
Converters = { new ConfigurationConverter() },
WriteIndented = true
});
或作为属性添加到 属性
public class MyThing
{
[JsonConverter(typeof(ConfigurationConverter))]
public IConfiguration Config { get; set; }
}
祝你好运!
我将一些 IConfiguration 作为 json 存储在我的 sqlserver 数据库中,这样我就可以将它们绑定到一些已经构建的 classes 以提供动态设置。
有时我可能会在运行时更改新的绑定属性,然后更新数据库。问题是,当我需要时,class 可能有更多不应绑定且不应序列化的属性。因此,我将 IConfiguration 保留为 class 的 属性。我使用这种方法的另一个原因是我需要从已加载配置的 class 实例化其他子 classes,并在我这样做时将它们保存到数据库。
问题是,当我序列化一个 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();
}
编辑
我还尝试了一种不同的方法,将 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;
}
}
但是我丢失了给定 JsonElement 背后的隐式类型:
如果我序列化生成的字典,我会得到类似“属性”的东西:“True”而不是“属性”:true
首先,原因
尝试以这种方式序列化 IConfiguration
不会按您希望的方式工作。让我们来探究一下原因。
序列化接口
部分 你没有属性的原因是因为 Serialize
的通用类型参数是 IConfiguration
。换句话说,您正在调用:
JsonSerializer.Serialize<IConfiguration>(updatedCfg)
当 System.Text.Json 使用通用参数序列化时,它仅(默认情况下没有任何自定义转换器)序列化该接口的 public 属性。在这种情况下 IConfiguration
没有 public 属性(索引器除外),因此您的输出为空 json.
使用运行时类型信息
现在,一般来说 要解决这个问题,您可以使用 non-generic overload 并传递类型。例如,它看起来像:
JsonSerializer.Serialize(updatedCfg, updatedCfg.GetType());
或者使用 object
作为类型参数:
JsonSerializer.Serialize<object>(updatedCfg);
System.Text.Json 然后将使用 运行时类型 信息来确定要序列化的属性。
ConfigurationRoot
现在你的问题的第二部分是,不幸的是,由于配置系统的设计方式,这仍然无法正常工作。 ConfigurationRoot
class(Build
的结果)可以聚合许多配置源。数据单独存储在每个提供商内部(甚至外部)。当您从配置中请求一个值时,它会循环遍历每个提供程序以找到匹配项。
所有这一切都表明 IConfiguration
对象的 concrete/runtime 类型仍然没有您希望序列化的 public 属性。事实上,在这种情况下传递运行时类型比模仿接口的行为更糟糕,因为它将尝试序列化唯一的 public 属性键入 (ConfigurationRoot.Providers
). This will give you a list of serialized providers, each typed as IConfigurationProvider
并具有零个 public 属性。
一个潜在的解决方案
由于您尝试序列化最终绑定到对象的配置,解决方法是重新序列化 that 对象:
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<KeyValuePair<string, string>>
。稍后可以将其反序列化并传递给 AddInMemoryCollection
您将需要 Microsoft.Extensions.Configuration.Abstractions package(可能已经被传递引用)和以下 using
指令:
using Microsoft.Extensions.Configuration;
然后您可以创建所有值的列表(使用 Section:Key
格式的键)
var configAsList = cfg.AsEnumerable().ToList();
var json = JsonSerializer.Serialize(configAsList);
或者您可以使用 ToDictionary
并将其序列化。
var configAsDict = cfg.AsEnumerable().ToDictionary(c => c.Key, c => c.Value);
var json = JsonSerializer.Serialize(configAsDict);
两种格式都适用于 AddInMemoryCollection
,因为它只需要一个 IEnumerable<KeyValuePair<string, string>>
(这两种类型都是)。但是,如果您希望使用 AddJsonFile/Stream
,您可能需要字典格式,因为我 认为 不支持 key/value 对数组。
字符串,字符串,只有字符串
您的印象似乎是 IConfiguration
对象正在存储 int
s、bool
s 等(例如)对应于 JSON 元素类型。这是不正确的。 IConfiguration
中的所有数据都以 字符串化形式 存储。基本配置提供程序 class 都需要 IDictionary<string, string>
填充数据。甚至 JSON 配置提供程序 perform an explicit ToString
上的值。
stringy-typed 值在调用 Bind
, Get<>
或 GetValue<>
。这些使用配置 binder,后者又使用注册的 TypeConverters
和众所周知的字符串解析方法。但在幕后,一切仍然是一个字符串。这意味着您的 json 文件是否包含值为 "True"
的字符串 属性 或值为 true
的布尔值 属性 并不重要。当映射到 boolean
属性.
使用上述字典序列化方法将按预期工作。
只是 运行 进入类似的需求。序列化 IConfiguration
以通过总线发送。这是我想出的
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
namespace A6k
{
public class ConfigurationConverter : JsonConverter<IConfiguration>
{
public override IConfiguration Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var root = new ConfigurationRoot(new List<IConfigurationProvider>(new[] { new MemoryConfigurationProvider(new MemoryConfigurationSource()) }));
var pathParts = new Stack<string>();
string currentProperty = null;
string currentPath = null;
while (reader.Read() && (reader.TokenType != JsonTokenType.EndObject || pathParts.Count > 0))
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
currentProperty = reader.GetString();
break;
case JsonTokenType.String:
if (pathParts.Count == 0)
root[currentProperty] = reader.GetString();
else
root[ConfigurationPath.Combine(currentPath, currentProperty)] = reader.GetString();
break;
case JsonTokenType.StartObject:
pathParts.Push(currentProperty);
currentPath = ConfigurationPath.Combine(pathParts);
break;
case JsonTokenType.EndObject:
pathParts.Pop();
currentPath = ConfigurationPath.Combine(pathParts);
break;
}
}
return root;
}
public override void Write(Utf8JsonWriter writer, IConfiguration value, JsonSerializerOptions options)
{
if (value is IConfigurationSection section)
{
if (section.Value is null)
writer.WriteStartObject(section.Key);
else
{
writer.WriteString(section.Key, section.Value);
return;
}
}
else
writer.WriteStartObject();
foreach (var child in value.GetChildren())
Write(writer, child, options);
writer.WriteEndObject();
}
}
}
它在反序列化时只使用一个内存配置提供程序。但结果 IConfiguration
的行为方式与发送的实例相同。
只需将其作为转换器添加到您的序列化中即可:
var json = JsonSerializer.Serialize(configuration, new JsonSerializerOptions
{
Converters = { new ConfigurationConverter() },
WriteIndented = true
});
或作为属性添加到 属性
public class MyThing
{
[JsonConverter(typeof(ConfigurationConverter))]
public IConfiguration Config { get; set; }
}
祝你好运!