我将原始JSON插入到集合中,发现存储在数据库中的内容缺少值。例如,我的集合是BsonDocuments
的集合:
_products = database.GetCollection<BsonDocument>("products");
将JSON插入集合的代码:
public int AddProductDetails(JObject json)
{
var doc = json.ToBsonDocument(DictionarySerializationOptions.Document);
_products.Insert(doc);
}
传入的JSON看起来像这样:
{
"Id": 1,
"Tags": [
"book",
"database"
],
"Name": "Book Name",
"Price": 12.12
}
但是,在集合中持久化的只是没有值的属性。
{
"_id": {
"$oid": "5165c7e10fdb8c09f446d720"
},
"Id": [],
"Tags": [
[],
[]
],
"Name": [],
"Price": []
}
为什么要删除这些值?
这正是我所期望的。
public int AddProductDetails(JObject json)
{
BsonDocument doc = BsonDocument.Parse(json.ToString());
_products.Insert(doc);
}
当我有一个属性为JObject
的c#类时,我遇到了这个问题。
我的解决方案是为MondoDB创建JObjectSerializer
并将属性添加到属性中,以便Mongo序列化器使用它。我想如果我足够努力的话,我可以在Mongo中注册下面的序列化器作为这个类型的全局序列化器。
属性处理的寄存器序列化器:
[BsonSerializer(typeof(JObjectSerializer))]
public JObject AdditionalData { get; set; }
序列化器本身:
public class JObjectSerializer : SerializerBase<JObject> // IBsonSerializer<JObject>
{
public override JObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var myBSONDoc = BsonDocumentSerializer.Instance.Deserialize(context);
return JObject.Parse(myBSONDoc.ToString());
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JObject value)
{
var myBSONDoc = MongoDB.Bson.BsonDocument.Parse(value.ToString());
BsonDocumentSerializer.Instance.Serialize(context, myBSONDoc);
}
}
使用JObject.ToString
, BsonDocument.Parse
等时的问题是性能不是很好,因为您多次执行相同的操作,您执行字符串分配,解析等。
所以,我写了一个函数,将JObject
转换为IEnumerable<KeyValuePair<string, object>>
(仅使用枚举),这是BsonDocument
构造函数之一可用的类型。下面是代码:
public static BsonDocument ToBsonDocument(this JObject jo)
{
if (jo == null)
return null;
return new BsonDocument(ToEnumerableWithObjects(jo));
}
public static IEnumerable<KeyValuePair<string, object>> ToEnumerableWithObjects(this JObject jo)
{
if (jo == null)
return Enumerable.Empty<KeyValuePair<string, object>>();
return new JObjectWrapper(jo);
}
private class JObjectWrapper : IEnumerable<KeyValuePair<string, object>>
{
private JObject _jo;
public JObjectWrapper(JObject jo)
{
_jo = jo;
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => new JObjectWrapperEnumerator(_jo);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public static object ToValue(JToken token)
{
object value;
switch (token.Type)
{
case JTokenType.Object:
value = new JObjectWrapper((JObject)token);
break;
case JTokenType.Array:
value = new JArrayWrapper((JArray)token);
break;
default:
if (token is JValue jv)
{
value = ((JValue)token).Value;
}
else
{
value = token.ToString();
}
break;
}
return value;
}
}
private class JArrayWrapper : IEnumerable
{
private JArray _ja;
public JArrayWrapper(JArray ja)
{
_ja = ja;
}
public IEnumerator GetEnumerator() => new JArrayWrapperEnumerator(_ja);
}
private class JArrayWrapperEnumerator : IEnumerator
{
private IEnumerator<JToken> _enum;
public JArrayWrapperEnumerator(JArray ja)
{
_enum = ja.GetEnumerator();
}
public object Current => JObjectWrapper.ToValue(_enum.Current);
public bool MoveNext() => _enum.MoveNext();
public void Reset() => _enum.Reset();
}
private class JObjectWrapperEnumerator : IEnumerator<KeyValuePair<string, object>>
{
private IEnumerator<KeyValuePair<string, JToken>> _enum;
public JObjectWrapperEnumerator(JObject jo)
{
_enum = jo.GetEnumerator();
}
public KeyValuePair<string, object> Current => new KeyValuePair<string, object>(_enum.Current.Key, JObjectWrapper.ToValue(_enum.Current.Value));
public bool MoveNext() => _enum.MoveNext();
public void Dispose() => _enum.Dispose();
public void Reset() => _enum.Reset();
object IEnumerator.Current => Current;
}
您是否尝试过使用BsonSerializer?
using MongoDB.Bson.Serialization;
[...]
var document = BsonSerializer.Deserialize<BsonDocument>(json);
BsonSerializer与字符串一起工作,所以如果JSON参数是一个jobobject(或JArray, JRaw等),你必须用JsonConvert.SerializeObject()
这是Andrew DeVries的回答的更新版本,包括处理序列化/反序列化空值。
public class JObjectSerializer : SerializerBase<JObject>
{
public override JObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
if (context.Reader.CurrentBsonType != BsonType.Null)
{
var myBSONDoc = BsonDocumentSerializer.Instance.Deserialize(context);
return JObject.Parse(myBSONDoc.ToStrictJson());
}
else
{
context.Reader.ReadNull();
return null;
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JObject value)
{
if (value != null)
{
var myBSONDoc = BsonDocument.Parse(value.ToString());
BsonDocumentSerializer.Instance.Serialize(context, myBSONDoc);
}
else
{
context.Writer.WriteNull();
}
}
}
ToStrictJson()
调用是一个扩展方法,它封装了对内置BSON ToJson()
方法的调用,包括将输出模式设置为strict。如果不这样做,解析将失败,因为BSON类型构造函数将保留在JSON输出中(例如ObjectId()
)。
下面是ToStrictJson()
的实现:
public static class MongoExtensionMethods
{
/// <summary>
/// Create a JsonWriterSettings object to use when serializing BSON docs to JSON.
/// This will force the serializer to create valid ("strict") JSON.
/// Without this, Object IDs and Dates are ouput as {"_id": ObjectId(ds8f7s9d87f89sd9f8d9f7sd9f9s8d)}
/// and {"date": ISODate("2020-04-14 14:30:00:000")} respectively, which is not valid JSON
/// </summary>
private static JsonWriterSettings jsonWriterSettings = new JsonWriterSettings()
{
OutputMode = JsonOutputMode.Strict
};
/// <summary>
/// Custom extension method to convert MongoDB objects to JSON using the OutputMode = Strict setting.
/// This ensure that the resulting string is valid JSON.
/// </summary>
/// <typeparam name="TNominalType">The type of object to convert to JSON</typeparam>
/// <param name="obj">The object to conver to JSON</param>
/// <returns>A strict JSON string representation of obj.</returns>
public static string ToStrictJson<TNominalType>(this TNominalType obj)
{
return BsonExtensionMethods.ToJson<TNominalType>(obj, jsonWriterSettings);
}
}
我使用以下代码。它是基于Simon的回答,谢谢你的想法,并以相同的方式工作,避免不必要的序列化/反序列化成字符串。
由于Linq和c# 10,它只是更紧凑了一点:
public static BsonDocument ToBsonDocument(this JObject o) =>
new(o.Properties().Select(p => new BsonElement(p.Name, p.Value.ToBsonValue())));
public static BsonValue ToBsonValue(this JToken t) =>
t switch
{
JObject o => o.ToBsonDocument(),
JArray a => new BsonArray(a.Select(ToBsonValue)),
JValue v => BsonValue.Create(v.Value),
_ => throw new NotSupportedException($"ToBsonValue: {t}")
};
这里的大多数答案都涉及到对字符串进行序列化,然后对字符串进行反序列化。这里是一个解决方案,序列化到/从原始BSON。它需要Newtonsoft.Json.Bson
nuget包。
using System.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
namespace Zonal.EventPublisher.Worker
{
public class JObjectSerializer : SerializerBase<JObject>
{
public override JObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
using (var stream = new MongoDB.Bson.IO.ByteBufferStream(context.Reader.ReadRawBsonDocument()))
using (JsonReader reader = new BsonDataReader(stream))
{
return JObject.Load(reader);
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JObject value)
{
using (var stream = new MemoryStream())
using (JsonWriter writer = new BsonDataWriter(stream))
{
value.WriteTo(writer);
var buffer = new MongoDB.Bson.IO.ByteArrayBuffer(stream.ToArray());
context.Writer.WriteRawBsonDocument(buffer);
}
}
}
}
不要忘记注册序列化器:
BsonSerializer.RegisterSerializer(new JObjectSerializer());
之后,您可以使用MongoDB.Bson.BsonExtensionMethods.ToBsonDocument
扩展方法将您的jobobject转换为BsonDocument:
var myBsonDocument = myJObject.ToBsonDocument()
并通过使用MongoDB.Bson.Serialization.BsonSerializer
类将BsonDocument转换回jobobject:
var myJObject = BsonSerializer.Deserialize<JObject>(myBsonDocument);