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

时间:2020-02-25 18:55:19

标签: c# json .net-core deserialization 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; }
}

示例代码

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并重新序列化的选项,因为我担心会引入不希望的更改。

3 个答案:

答案 0 :(得分:8)

找到了一种正确方法来正确读取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在回答中指出,模型表示和序列化之间并没有混合。

答案 1 :(得分:4)

您可以为此使用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();
    }
}

答案 2 :(得分:1)

已接受答案的快速附录:

如果您还需要编写原始 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 方法这一事实的“正确”方法。