System.Text.Json - 将嵌套对象反序列化为字符串
Posted
技术标签:
【中文标题】System.Text.Json - 将嵌套对象反序列化为字符串【英文标题】:System.Text.Json - Deserialize nested object as string 【发布时间】:2020-06-09 14:09:32 【问题描述】:我正在尝试使用 System.Text.Json.JsonSerializer
部分反序列化模型,因此其中一个属性被读取为包含原始 JSON 的字符串。
public class SomeModel
public int Id get; set;
public string Name get; set;
public string Info get; set;
示例代码
var json = @"
""Id"": 1,
""Name"": ""Some Name"",
""Info"":
""Additional"": ""Fields"",
""Are"": ""Inside""
";
var model = JsonSerializer.Deserialize<SomeModel>(json);
应该生成模型,其中Info
属性包含来自原始JSON的Info对象作为字符串:
"Additional": "Fields",
"Are": "Inside"
它不能开箱即用并引发异常:
System.Text.Json.JsonException: ---> System.InvalidOperationException: 无法将令牌类型“StartObject”的值作为字符串获取。
到目前为止我尝试了什么:
public class InfoToStringConverter : JsonConverter<string>
public override string Read(
ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
return reader.GetString();
public override void Write(
Utf8JsonWriter writer, string value, JsonSerializerOptions options)
throw new NotImplementedException();
并将其应用到模型中
[JsonConverter(typeof(InfoToStringConverter))]
public string Info get; set;
并将选项添加到JsonSerializer
var options = new JsonSerializerOptions();
options.Converters.Add(new InfoToStringConverter());
var model = JsonSerializer.Deserialize<SomeModel>(json, options);
仍然会引发相同的异常:
System.Text.Json.JsonException: ---> System.InvalidOperationException: 无法将令牌类型“StartObject”的值作为字符串获取。
什么是烹饪我需要的东西的正确食谱?它使用Newtonsoft.Json
以类似的方式工作。
更新
对我来说,保持嵌套的 JSON 对象尽可能原始很重要。所以,我会避免像反序列化为 Dictionary
并序列化回来这样的选项,因为我害怕引入不需要的更改。
【问题讨论】:
您还可以创建一个类并将Info
值存储在其实例中,就像在此thread 中所做的那样
要使用自定义转换器实现预期行为,您可以查看JsonValueConverterKeyValuePair
并了解如何正确读写复杂json结构的StartObject
和EndObject
【参考方案1】:
找到了正确读取JsonConverter
中嵌套JSON 对象的正确方法。完整的解决方案如下:
public class SomeModel
public int Id get; set;
public string Name get; set;
[JsonConverter(typeof(InfoToStringConverter))]
public string Info get; set;
public class InfoToStringConverter : JsonConverter<string>
public override string Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
using (var jsonDoc = JsonDocument.ParseValue(ref reader))
return jsonDoc.RootElement.GetRawText();
public override void Write(
Utf8JsonWriter writer, string value, JsonSerializerOptions options)
throw new NotImplementedException();
在代码本身甚至不需要创建选项:
var json = @"
""Id"": 1,
""Name"": ""Some Name"",
""Info"":
""Additional"": ""Fields"",
""Are"": ""Inside""
";
var model = JsonSerializer.Deserialize<SomeModel>(json);
Info
属性中的原始 JSON 文本甚至包含示例中引入的额外空格以提高可读性。
正如@PavelAnikhouski 在他的回答中所说,模型表示及其序列化没有混合。
【讨论】:
干得好,您已经知道如何使用自定义转换器了:) 快速说明,如果没有Write
实现,您将无法将其序列化回来【参考方案2】:
您可以为此使用JsonExtensionData
属性并在模型中声明Dictionary<string, JsonElement>
或Dictionary<string, object>
属性来存储此信息
public class SomeModel
public int Id get; set;
public string Name get; set;
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData get; set;
[JsonIgnore]
public string Data
get
return ExtensionData?["Info"].GetRawText();
然后您可以添加一个附加属性以通过Info
键从该字典中获取一个字符串。在上面的代码中,Data
属性将包含预期的字符串
"Additional": "Fields",
"Are": "Inside"
由于某些原因,添加具有相同名称的属性 Info
不起作用,即使使用 JsonIgnore
。详情请查看Handle overflow JSON。
您还可以将Info
属性声明为JsonElement
类型并从中获取原始文本
public class SomeModel
public int Id get; set;
public string Name get; set;
public JsonElement Info get; set;
var model = JsonSerializer.Deserialize<SomeModel>(json);
var rawString = model.Info.GetRawText();
但它会导致模型表示及其序列化的混合。
另一种选择是使用JsonDocument
解析数据,枚举属性并逐个解析,就像这样
var document = JsonDocument.Parse(json);
foreach (var token in document.RootElement.EnumerateObject())
if (token.Value.ValueKind == JsonValueKind.Number)
if(token.Value.TryGetInt32(out int number))
if (token.Value.ValueKind == JsonValueKind.String)
var stringValue = token.Value.GetString();
if (token.Value.ValueKind == JsonValueKind.Object)
var rawContent = token.Value.GetRawText();
【讨论】:
【参考方案3】:已接受答案的快速附录:
如果您还需要编写原始 JSON 值,这里是转换器的 Write
方法的实现:
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
using (JsonDocument document = JsonDocument.Parse(value))
document.RootElement.WriteTo(writer);
正如github 上的dotnet 运行时存储库中所述,这似乎是解决他们决定不实现WriteRawValue
方法这一事实的“正确”方法。
【讨论】:
以上是关于System.Text.Json - 将嵌套对象反序列化为字符串的主要内容,如果未能解决你的问题,请参考以下文章
如何反序列化作为 System.Text.Json 中的字符串的嵌套 JSON 对象?
如何使用 System.Text.Json 序列化/反序列化非枚举类型的嵌套字典?
如何让 System.Text.Json 将对象反序列化为其原始类型?
如何使用 System.Text.Json API 将流反序列化为对象