I am trying to deserialize a Bookmarks file. Specifically using polymorphic deserialization without Newtonsoft. When running I get an exception in the Read method of the converter class, at return for case "folder". It looks like I need some sort of constructor for either folder or its base class. I tried using the [JsonConstructor] attribute in each class but no luck.
Additionally, when I omit the getter and setter for Folder's FolderElement list the program compiles and runs but in the JSON output only the objects of type 'folder' are created, and they are missing the 'children' properties.
Model
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 Converter
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);
}
}
}
Sample Bookmarks file
{
"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
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'.