使用ASP.NET Core MongoDB驱动反序列化时遇JsonException求助
问题概述
使用ASP.NET Core MongoDB驱动反序列化BSON文档时,调用JsonSerializer.DeserializeAsync抛出System.Text.Json.JsonException,错误信息为“'O' is an invalid start of a value”,已验证JSON结构和MongoDB文档仍未解决。当前需保存动态Schema的JSON文档,采用先序列化JSON再转BSON存储、读取时转JSON再反序列化的实现方式。
错误堆栈
System.Text.Json.JsonException: 'O' is an invalid start of a value. Path: $._id | LineNumber: 0 | BytePositionInLine: 10. ---> System.Text.Json.JsonReaderException: 'O' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 10. at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes) at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker) at System.Text.Json.Utf8JsonReader.ReadSingleSegment() at System.Text.Json.Utf8JsonReader.Read() at System.Text.Json.Utf8JsonReader.Skip() at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) --- End of inner exception stack trace --- at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex) at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount) at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo) at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
相关代码
控制器代码
[HttpGet("{id}")] public async Task<ActionResult<ProjectIdentification>> GetProjectById(string id) { var project = await _projectService.GetProjectById(id); if (project == null) { return NotFound(); } return Ok(project.ToJson()); }
实体类代码
public class ProjectIdentification { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string? Id { get; set; } = ObjectId.GenerateNewId().ToString(); public string? version { get; set; } public Object[] content { get; set; } public Object[] columns { get; set; } public Theme theme { get; set; } }
服务层代码
public async Task AddProject(ProjectIdentification project) { string serializedJson = JsonSerializer.Serialize(project, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); BsonDocument bsonDocument = BsonDocument.Parse(serializedJson); await _collection.InsertOneAsync(bsonDocument); } public async Task<ProjectIdentification> GetProjectById(string id) { var filter = Builders<BsonDocument>.Filter.Eq("_id", ObjectId.Parse(id)); var result = await _collection.Find(filter).FirstOrDefaultAsync(); Console.Write(result); if (result != null) { return JsonSerializer.Deserialize<ProjectIdentification>(result.ToJson()); } return null; }
数据示例
{ "_id": { "$oid": "65edc0c6482da8bd8368fbbf" }, "Id": "65edc0c6482da8bd8368fbbe", "content": [ { "id": "z5LllRt-NoiGkoisa_2tN", "type": "image", "data": { "style": "circle", "image": "/images/profile.jpg" } }, { "id": "QfAFa0golVKIImL764OCO", "type": "text", "data": { "headline": "Horst Karacho", "description": "My name is Horst :)", "rank":"test", "anotherfield":"tesst" } }, { "id": "j3ofcLmYExoG0AS_yoRq0", "type": "text", "data": { "headline": "Lorem Ipsum", "description": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna...", "details": { "author": "Unknown", "publishedDate": "2023-01-15", "wordCount": 245 } } }, ], "columns": [ { "id": "mOceUBR-oB5xEzfstycFh", "content": [ "qkx4WlS5XkUZcfTpy9rN0", "qkx4WlS5XkUZcfTpy9rN1", "qkx4WlS5XkUZcfTpy9rN11", "qkx4WlS5XkUZcfTpy9rN12", "qkx4WlS5XkUZcfTpy9rN3", "qkx4WlS5XkUZcfTpy9rN4", "qkx4WlS5XkUZcfTpy9rN5", "qkx4WlS5XkUZcfTpy9rN6", "qkx4WlS5XkUZcfTpy9rN7", "qkx4WlS5XkUZcfTpy9rN8", "qkx4WlS5XkUZcfTpy9rN9" ] } ], "theme": { "HighlightColor": "#3f51b5", "HeadlineColor": "#95A5A6", "HeadlineFontType": "Roboto", "HeadlineFontSize": "1.8em", "HeadlineFontWeight": "600", "HeadlineBorderType": "dashed", "TextColor": "#333", "TextFontType": "Source Sans Pro", "TextFontSize": "1em", "TextFontWeight": "200" } }
解决思路与建议
1. 错误根源
问题出在result.ToJson()生成的是MongoDB扩展JSON格式(如{"$oid": "xxx"}),而System.Text.Json无法识别这种非标准的JSON结构,解析到$oid的'O'字符时抛出错误。
2. 解决方案一:使用MongoDB驱动直接反序列化BSON
无需将BSON转JSON再反序列化,直接用MongoDB驱动的BsonSerializer完成BSON到实体类的转换,这是最直接的方案:
修改服务层的GetProjectById方法:
public async Task<ProjectIdentification> GetProjectById(string id) { var filter = Builders<BsonDocument>.Filter.Eq("_id", ObjectId.Parse(id)); var result = await _collection.Find(filter).FirstOrDefaultAsync(); if (result != null) { // 使用MongoDB驱动的序列化器直接转换 return BsonSerializer.Deserialize<ProjectIdentification>(result); } return null; }
同时,添加实体类的Bson映射配置(可在Startup/Program中初始化):
BsonClassMap.RegisterClassMap<ProjectIdentification>(cm => { cm.AutoMap(); cm.MapIdMember(c => c.Id) .SetSerializer(new StringSerializer(BsonType.ObjectId)); });
3. 解决方案二:转换BSON为标准JSON
如果坚持要转JSON再用System.Text.Json解析,需要将MongoDB扩展JSON转换为标准JSON,可使用JsonWriterSettings配置:
修改GetProjectById中的result.ToJson():
var json = result.ToJson(new JsonWriterSettings { OutputMode = JsonOutputMode.Strict }); return JsonSerializer.Deserialize<ProjectIdentification>(json);
JsonOutputMode.Strict会将$oid这类扩展类型直接转换为字符串,生成标准JSON格式,让System.Text.Json可以正常解析。
4. 优化动态Schema的处理
针对动态Schema的需求,实体类中的content和columns字段可以用BsonDocument[]替代Object[],这样MongoDB驱动可以更好地处理动态结构,同时避免序列化/反序列化时的类型问题:
public BsonDocument[] content { get; set; } public BsonDocument[] columns { get; set; }
如果需要将BsonDocument转换为.NET对象,可后续再用BsonSerializer或手动转换为动态对象(如dynamic或Dictionary<string, object>)。
内容的提问来源于stack exchange,提问作者PASHA

