System.Text.Json - 将嵌套对象反序列化为字符串

Kyr*_*o M 9 c# json deserialization .net-core system.text.json

我正在尝试使用System.Text.Json.JsonSerializer来部分反序列化模型,因此其中一个属性被读取为包含原始 JSON 的字符串。

public class SomeModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Info { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

示例代码

var json = @"{
                 ""Id"": 1,
                 ""Name"": ""Some Name"",
                 ""Info"": {
                     ""Additional"": ""Fields"",
                     ""Are"": ""Inside""
                 }
             }";

var model = JsonSerializer.Deserialize<SomeModel>(json);
Run Code Online (Sandbox Code Playgroud)

应该生成模型,该Info属性包含来自原始 JSON 的 Info 对象作为字符串:

{
    "Additional": "Fields",
    "Are": "Inside"
}
Run Code Online (Sandbox Code Playgroud)

它不能开箱即用并引发异常:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其应用到模型中

[JsonConverter(typeof(InfoToStringConverter))]
public string Info { get; set; }
Run Code Online (Sandbox Code Playgroud)

并添加选项 JsonSerializer

var options = new JsonSerializerOptions();
options.Converters.Add(new InfoToStringConverter());
var model = JsonSerializer.Deserialize<SomeModel>(json, options);
Run Code Online (Sandbox Code Playgroud)

尽管如此,它还是会抛出相同的异常:

System.Text.Json.JsonException: ---> System.InvalidOperationException: 无法以字符串形式获取标记类型“StartObject”的值。

烹饪我需要的食物的正确食谱是什么?它以类似的方式使用Newtonsoft.Json.

更新

对我来说,保持嵌套的 JSON 对象尽可能原始很重要。因此,我会避免使用反序列化为Dictionary和序列化回之类的选项,因为我害怕引入不需要的更改。

Kyr*_*o M 20

找到了如何正确读取 .json 文件中嵌套的 JSON 对象的正确方法JsonConverter。完整的解决方案如下:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在代码本身中,甚至不需要创建选项:

var json = @"{
                 ""Id"": 1,
                 ""Name"": ""Some Name"",
                 ""Info"": {
                     ""Additional"": ""Fields"",
                     ""Are"": ""Inside""
                 }
             }";

var model = JsonSerializer.Deserialize<SomeModel>(json);
Run Code Online (Sandbox Code Playgroud)

Info属性中的原始 JSON 文本甚至包含示例中引入的额外空格,以提高可读性。

并且没有混合模型表示及其序列化,正如@PavelAnikhouski 在他的回答中所说。

  • 干得好,您已经知道如何使用自定义转换器来做到这一点:) 简单说明一下,如果没有“Write”实现,您将无法将其序列化回来 (4认同)
  • 这太棒了,谢谢。如果该值为 null ,您可能会稍微提高性能: if(reader.TokenType == JsonTokenType.Null) { return null; } (2认同)

Pav*_*ski 6

您可以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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以添加一个额外的属性来通过Info键从这个字典中获取一个字符串。在上面的代码中,Data属性将包含预期的字符串

{
    "Additional": "Fields",
    "Are": "Inside"
}
Run Code Online (Sandbox Code Playgroud)

由于某些原因,添加具有相同名称的属性Info不起作用,即使使用JsonIgnore. 有关详细信息,请查看处理溢出 JSON

您还可以将Info属性声明为JsonElement类型并从中获取原始文本

{
    "Additional": "Fields",
    "Are": "Inside"
}
Run Code Online (Sandbox Code Playgroud)
public class SomeModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public JsonElement Info { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

但它会导致模型表示及其序列化的混合。

另一种选择是使用解析数据JsonDocument,枚举属性并逐一解析它们,就像这样

var model = JsonSerializer.Deserialize<SomeModel>(json);
var rawString = model.Info.GetRawText();
Run Code Online (Sandbox Code Playgroud)