System.Text.Json - 将嵌套对象反序列化为字符串
System.Text.Json - Deserialize nested object as string
我正在尝试使用 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 的信息对象作为字符串:
{
"Additional": "Fields",
"Are": "Inside"
}
开箱即用并抛出异常:
System.Text.Json.JsonException: ---> System.InvalidOperationException:
Cannot get the value of a token type 'StartObject' as a string.
到目前为止我尝试了什么:
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:
Cannot get the value of a token type 'StartObject' as a string.
烹饪我需要的食物的正确食谱是什么?它使用 Newtonsoft.Json
.
以类似的方式工作
更新
对我来说,尽可能保持嵌套 JSON 对象的原始状态很重要。所以,我会避免像反序列化为 Dictionary
和反序列化这样的选项,因为我害怕引入不需要的更改。
您可以为此使用 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();
}
}
找到了如何正确读取 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 在他的回答中所说,模型表示及其序列化没有混合。
已接受答案的快速补充:
如果您还需要写入原始 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.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 的信息对象作为字符串:
{
"Additional": "Fields",
"Are": "Inside"
}
开箱即用并抛出异常:
System.Text.Json.JsonException: ---> System.InvalidOperationException: Cannot get the value of a token type 'StartObject' as a string.
到目前为止我尝试了什么:
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: Cannot get the value of a token type 'StartObject' as a string.
烹饪我需要的食物的正确食谱是什么?它使用 Newtonsoft.Json
.
更新
对我来说,尽可能保持嵌套 JSON 对象的原始状态很重要。所以,我会避免像反序列化为 Dictionary
和反序列化这样的选项,因为我害怕引入不需要的更改。
您可以为此使用 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();
}
}
找到了如何正确读取 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 在他的回答中所说,模型表示及其序列化没有混合。
已接受答案的快速补充:
如果您还需要写入原始 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
方法这一事实的“正确”方法。