Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JObject.ToBsonDocument dropping values

Tags:

json.net

mlab

I'm inserting raw JSON into a collection and finding that what is stored in the database is missing the values. For example, my collection is a collection of BsonDocuments:

_products = database.GetCollection<BsonDocument>("products");

The code to insert the JSON into the collection:

public int AddProductDetails(JObject json)
{
    var doc = json.ToBsonDocument(DictionarySerializationOptions.Document);
    _products.Insert(doc);
}

The JSON that is passed in looks like this:

{
  "Id": 1,
  "Tags": [
    "book",
    "database"
  ],
  "Name": "Book Name",
  "Price": 12.12
}

But, what is persisted in the collection is just the properties with no values.

{
  "_id": {
    "$oid": "5165c7e10fdb8c09f446d720"
  },
  "Id": [],
  "Tags": [
    [],
    []
  ],
  "Name": [],
  "Price": []
}

Why are the values being dropped?

like image 860
Rick Rainey Avatar asked Apr 10 '13 20:04

Rick Rainey


4 Answers

This does what I was expecting.

    public int AddProductDetails(JObject json)
    {
        BsonDocument doc = BsonDocument.Parse(json.ToString());
        _products.Insert(doc);
    }
like image 122
Rick Rainey Avatar answered Nov 12 '22 22:11

Rick Rainey


I ran into this issue when I had a C# class with a property of type JObject.

My Solution was to create JObjectSerializer for MondoDB and add the attribute to the property so Mongo serializer uses it. I assume if I tried hard enough I could register the below serializer in Mongo as the global one for this type as well.

Register serializer for property processing:

[BsonSerializer(typeof(JObjectSerializer))]
public JObject AdditionalData { get; set; }

The serializer itself:

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);
    }
}
like image 42
Andrew DeVries Avatar answered Nov 12 '22 21:11

Andrew DeVries


The problem when using JObject.ToString, BsonDocument.Parse, etc. is the performance is not very good because you do the same operations multiple times, you do string allocations, parsing, etc.

So, I have written a function that converts a JObject to an IEnumerable<KeyValuePair<string, object>> (only using enumerations), which is a type usable by one of the BsonDocument constructors. Here is the code:

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;
}
like image 8
Simon Mourier Avatar answered Nov 12 '22 20:11

Simon Mourier


Have you tried using the BsonSerializer?

using MongoDB.Bson.Serialization;
[...]
var document = BsonSerializer.Deserialize<BsonDocument>(json);

BsonSerializer works with strings, so if the JSON argument is a JObject(or JArray, JRaw etc) you have to serialize it with JsonConvert.SerializeObject()

like image 3
Niccolò Campolungo Avatar answered Nov 12 '22 21:11

Niccolò Campolungo