我正在尝试使用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
并重新序列化的选项,因为我担心会引入不希望的更改。
答案 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
方法这一事实的“正确”方法。