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 方法这一事实的“正确”方法。