提问人:Gnome-Improvement713 提问时间:8/20/2023 最后编辑:dbcGnome-Improvement713 更新时间:8/20/2023 访问量:60
书签文件的 C# 多态反序列化
C# Polymorphic Deserialization of Bookmarks file
问:
我正在尝试反序列化书签文件。具体来说,使用没有 Newtonsoft 的多态反序列化。运行时,我在转换器类的 Read 方法中出现异常,返回大小写“文件夹”。看起来我需要某种文件夹或其基类的构造函数。我尝试在每个类中使用[JsonConstructor]属性,但没有运气。
此外,当我省略 Folder 的 FolderElement 列表的 getter 和 setter 时,程序会编译并运行,但在 JSON 输出中,仅创建“folder”类型的对象,并且它们缺少“children”属性。
型
public abstract class BookmarkElement
{
public BookmarkElement() {}
[JsonPropertyName("date_added")]
public string DateAdded { get; set; }
[JsonPropertyName("date_last_used")]
public string DateLastUsed { get; set; }
[JsonPropertyName("guid")]
public string Guid { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
}
public class Bookmark : BookmarkElement
{
public Bookmark() { }
[JsonPropertyName("url")]
public string Url {get; set;}
}
public class Folder : BookmarkElement
{
public Folder() {}
[JsonPropertyName("date_modified")]
public string DateModified { get; set; }
[JsonPropertyName("children")]
public List<BookmarkElement> FolderElements {get; set;}
}
public class Root : BookmarkElement
{
public Root() {}
[JsonPropertyName("date_modified")]
public string DateModified { get; set; }
[JsonPropertyName("children")]
public List<BookmarkElement> RootFolder { get; set; }
}
public class BookmarkModel
{
public BookmarkModel() {}
[JsonPropertyName("checksum")]
public string Checksum { get; set; }
[JsonPropertyName("roots")]
public Dictionary<string, Root> Roots { get; set; }
[JsonPropertyName("version")]
public int Version { get; set; }
}
JSON转换器
public class BookmarkElementConverter : JsonConverter<BookmarkElement>
{
public override bool CanConvert(Type typeToConvert) =>
typeof(BookmarkElement).IsAssignableFrom(typeToConvert);
public override BookmarkElement? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType != JsonTokenType.StartObject) throw new JsonException();
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
if(!jsonDocument.RootElement.TryGetProperty("type", out var typeProperty)) throw new JsonException();
var jsonType = jsonDocument.RootElement.GetRawText();
switch(typeProperty.GetString())
{
case "url":
return (Bookmark)JsonSerializer.Deserialize(jsonType, typeof(Bookmark));
case "folder":
return (Folder)JsonSerializer.Deserialize(jsonType, typeof(Folder));
default:
throw new JsonException();
}
}
}
public override void Write(Utf8JsonWriter writer, BookmarkElement value, JsonSerializerOptions options)
{
if (value is Bookmark bookmark)
{
JsonSerializer.Serialize(writer, bookmark);
}
else if (value is Folder folder)
{
JsonSerializer.Serialize(writer, folder);
}
}
}
示例书签文件
{
"checksum": "cc1f5c62ec7814f7928e2befab26c311",
"roots": {
"bookmark_bar": {
"children": [ {
"children": [ ],
"date_added": "13335767383821356",
"date_last_used": "0",
"date_modified": "13335767383821356",
"guid": "efeb5549-612d-4656-8982-a17069075213",
"id": "13",
"name": "test",
"type": "folder"
}, {
"date_added": "13335767548044529",
"date_last_used": "0",
"guid": "df7a482b-c1c5-4aa2-af8e-8cee539513d9",
"id": "15",
"name": "DuckDuckGo — Privacy, simplified.",
"type": "url",
"url": "https://duckduckgo.com/"
} ],
"date_added": "13335764354355200",
"date_last_used": "0",
"date_modified": "13335767548044529",
"guid": "0bc5d13f-2cba-5d74-951f-3f233fe6c908",
"id": "1",
"name": "Bookmarks bar",
"type": "folder"
},
"other": {
"children": [ ],
"date_added": "13335764354355201",
"date_last_used": "0",
"date_modified": "0",
"guid": "82b081ec-3dd3-529c-8475-ab6c344590dd",
"id": "2",
"name": "Other bookmarks",
"type": "folder"
},
"synced": {
"children": [ ],
"date_added": "13335764354355202",
"date_last_used": "0",
"date_modified": "0",
"guid": "4cf2e351-0e85-532b-bb37-df045d8f8d0f",
"id": "3",
"name": "Mobile bookmarks",
"type": "folder"
}
},
"version": 1
}
例外
Exception has occurred: CLR/System.NotSupportedException
An exception of type 'System.NotSupportedException' occurred in System.Text.Json.dll but was not handled in user code: 'Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'BookmarkReader.Model.BookmarkElement'. Path: $.children[0] | LineNumber: 1 | BytePositionInLine: 24.'
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.NotSupportedException : Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'BookmarkReader.Model.BookmarkElement'.
答:
导致该错误的原因是,在某些时候,您的转换器没有被拾取,并且您的代码正在尝试反序列化 类型的对象,该对象是抽象的,因此无法构造。Microsoft 的错误消息文本具有误导性,因此请参阅演示小提琴 #1,它完全省略了转换器以进行确认。BookmarkElement
要解决此问题,请进行修复,以便将传入的选项(将包含在转换器列表中)传递到 。您需要这样做,因为您的数据模型是递归的,因此转换器也必须以递归方式调用。您还必须修复为仅对抽象类型 BookmarkElement
返回 true。这是默认行为,因此您可以删除替代。对于派生的具体类型,转换器不是必需的,如果尝试将转换器用于派生类型,则会收到堆栈溢出异常。BookmarkElementConverter().Read()
BookmarkElementConverter
JsonSerializer.Deserialize()
BookmarkElement
CanConvert()
因此,应如下所示,并进行了一些代码简化:BookmarkElementConverter
public class BookmarkElementConverter : JsonConverter<BookmarkElement>
{
public override BookmarkElement? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
if(!jsonDocument.RootElement.TryGetProperty("type", out var typeProperty))
throw new JsonException();
return typeProperty.GetString() switch
{
"url" => jsonDocument.RootElement.Deserialize<Bookmark>(options),
"folder" => jsonDocument.RootElement.Deserialize<Folder>(options),
_ => throw new JsonException(),
};
}
}
public override void Write(Utf8JsonWriter writer, BookmarkElement value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
然后,当您反序列化时,请务必包含在 :BookmarkElementConverter
JsonSerializerOptions.Converters
var options = new JsonSerializerOptions
{
Converters = { new BookmarkElementConverter() },
// Add any additional required options here:
WriteIndented = true,
};
var model = JsonSerializer.Deserialize<BookmarkModel>(json, options);
在这里演示小提琴 #2。
评论
Type
public abstract string Type { get; }
评论
BookmarkElement
JsonSerializerOptions
JsonSerializer.Serialize()