Augustin Popa
Augustin Popa

Reputation: 1543

Convert MongoDB BsonDocument to valid JSON in C#

I am working with the MongoDB C# driver. I have a BsonDocument with some data which includes some MongoDB-specific types (like ObjectIDs and ISODates). I want to convert this to a valid general-purpose JSON string. In other words, I can't have something like _id: ObjectId(...) or date: ISODate(...) but would prefer _id: "..." and date: "...". Basically, I want to convert these special types that only MongoDB recognizes to regular strings so they can be parsed more easily. The problem is that a built-in function like .ToJson() (which another StackOverflow answer suggests) doesn't really convert the document to valid JSON at all because it maintains these special types. My document also contains many levels of arrays and sub-documents, so a simple for loop will not suffice. What's the best way to convert a BsonDocument that avoids this problem? I would prefer something built-in rather than manually recursing through the document to fix all the issues.

Upvotes: 42

Views: 65633

Answers (12)

Kristian Dimitrov
Kristian Dimitrov

Reputation: 53

You can create your own custom JsonConverter:

    public class BsonDocJsonConverter : JsonConverter<BsonDocument>
{
    public override BsonDocument? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string? jsonStr = reader.GetString();
        BsonDocument.TryParse(jsonStr, out BsonDocument? bson);
        return bson;
    }

    public override void Write(Utf8JsonWriter writer, BsonDocument value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

You can then place it on a property or type:

public class CommandResult
{
    [BsonElement("isSuccess")]
    public bool IsSuccess { get; set; }

    [BsonElement("message")]
    public string? Message { get; set; }

    [JsonConverter(typeof(BsonDocJsonConverter))]
    [BsonElement("data")]
    public BsonDocument? Data { get; set; }
}

This is a working solution, feel free to modify as you want...

Upvotes: 1

Jalal
Jalal

Reputation: 6856

Since Davide Icardi answer is deprecated so:

  1. Install Newtonsoft.Json.Bson package
  2. Replace BsonReader with BsonDataReader.

Your extension method should be like this:

using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using System.IO;
using System.Text;

namespace YourNamespaceGoesHere
{
    public static class BsonHelpers
    {
        public static string ToNormalJson(BsonDocument bson)
        {
            using (var stream = new MemoryStream())
            {
                using (var writer = new BsonBinaryWriter(stream))
                {
                    BsonSerializer.Serialize(writer, typeof(BsonDocument), bson);
                }
                stream.Seek(0, SeekOrigin.Begin);
                
                using (var reader = new BsonDataReader(stream))
                {
                    var sb = new StringBuilder();
                    var sw = new StringWriter(sb);
                    using (var jWriter = new JsonTextWriter(sw))
                    {
                        jWriter.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
                        jWriter.WriteToken(reader);
                    }
                    return sb.ToString();
                }
            }

        }

    }
}

This should generate the expected normal valid JSON string you're looking for :)

Upvotes: 1

Jordi
Jordi

Reputation: 2567

Through experimentation I discovered that there is an option that makes this method output proper JSON:

BsonDocument myBsonDocument = ... //code that loads a BSON document
myBsonDocument.ToJson(new JsonWriterSettings { OutputMode = JsonOutputMode.RelaxedExtendedJson})

Result:

{ "_id" : { "$oid" : "5fb7a33e73152101d6610e9d" }, "moreProperties" : "moreValues" }

Upvotes: 10

sandiejat
sandiejat

Reputation: 3161

MongoDB.Bson (2.5+) has support to map between BsonValues and .Net objects. BsonTypeMapper Class

To map a BsonValue (or BsonDocument) to .Net object use

var dotNetObj = BsonTypeMapper.MapToDotNetValue(bsonDoc);

You can then use your choice of serialization library. For example,

JsonConvert.SerializeObject(dotNetObj);

If you have a List of BsonDocument

var dotNetObjList = bsonDocList.ConvertAll(BsonTypeMapper.MapToDotNetValue);

Upvotes: 56

T Brown
T Brown

Reputation: 1543

My problem had to do with how DotNet Core WebAPI serializes an object to json. If you return a string from a method that is formatted as json, WEBAPI will serialize it to json again. This is only needed if you are working with a generic BsonDocument to save to MongoDb.

[HttpGet()]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<string>> GetAsync()
{
    return Ok(ret.ToJson());
}

Fix

[HttpGet()]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<object>> GetAsync()
{
    var doc = await _collection.Find(...).FirstOrDefaultAsync();
    return Ok(JObject.Parse(doc.ToJson()));
}

Upvotes: 1

jidh
jidh

Reputation: 172

If the contents of the BSON document is saved as, below

{
"Date" : "2019-04-05T07:07:31.979Z",
"BSONCONTENT" : {
    "_t" : "MongoDB.Bson.BsonDocument, MongoDB.Bson",
    "_v" : {
        "A" : "XXXX",
        "B" : 234   
           }  
 }     

}

then it works with generic class.

private static T ProcessBsonConversion<T>(BsonDocument data)
    {
        var content = data.GetElement("_v");
        var jsonDataContent= content.Value.AsBsonValue.ToJson();
        return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonDataContent);

    }

Upvotes: 1

wutzebaer
wutzebaer

Reputation: 14875

what about

String json = result.toJson(JsonWriterSettings.builder().objectIdConverter(new Converter<ObjectId>() {
            @Override
            public void convert(ObjectId value, StrictJsonWriter writer) {
                writer.writeString(value.toHexString());
            }
        }).build());

Upvotes: 0

Piotr Kula
Piotr Kula

Reputation: 9841

If you need to use this ASP.NET Core for when you may be returning a model that has BsonDocument to be able to add dynamic data. You can use this JsonConverter implementation based on MarkKGreenway's answer!

 public class BsonDocumentJsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(BsonDocument);
        }

        public override bool CanRead
        {
            get
            {
                return false;
            }
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            //string json = (value as BsonDocument).ToJson(); //!NB: this returns BSON not JSON. Why on earth is it called ToJson!?
            string json = JsonConvert.SerializeObject(value);
            writer.WriteRawValue(json);
        }
    }

Then in your Startup.cs just add the following.

  services.AddMvc()
                .AddJsonOptions(options => options.SerializerSettings.Converters.Add(new BsonDocumentJsonConverter()));

Upvotes: 1

Davide Icardi
Davide Icardi

Reputation: 12229

In my opinion the best option is to use Newtonsoft.Json.Bson.BsonReader. Here a complete example:

public string ToJson(BsonDocument bson)
{
    using (var stream = new MemoryStream())
    {
        using (var writer = new BsonBinaryWriter(stream))
        {
            BsonSerializer.Serialize(writer, typeof(BsonDocument), bson);
        }
        stream.Seek(0, SeekOrigin.Begin);
        using (var reader = new Newtonsoft.Json.Bson.BsonReader(stream))
        {
            var sb = new StringBuilder();
            var sw = new StringWriter(sb);
            using (var jWriter = new JsonTextWriter(sw))
            {
                jWriter.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
                jWriter.WriteToken(reader);
            }
            return sb.ToString();
        }
    }
}

I think that this should handle all cases correctly (dates, ids, ...).

Upvotes: 13

Erkko V&#228;lja
Erkko V&#228;lja

Reputation: 31

Here is the way i did it, to skip mongodb _id entry.

var collection = _database.GetCollection<BsonDocument>("test");

var result = await collection.Find(new BsonDocument())
     .Project(Builders<BsonDocument>.Projection.Exclude("_id"))
     .ToListAsync();
var obj = result.ToJson();

Upvotes: 2

drye
drye

Reputation: 1402

I've ran into the same thing, you can get valid JSON via:

var jsonWriterSettings = new JsonWriterSettings { OutputMode = JsonOutputMode.Strict };
JObject json = JObject.Parse(postBsonDoc.ToJson<MongoDB.Bson.BsonDocument>(jsonWriterSettings));

However it will return something like:

{"_id":{"$oid":"559843798f9e1d0fe895c831"}, "DatePosted":{"$date":1436107641138}}

I'm still trying to find a way to flatten that.

Upvotes: 22

MarkKGreenway
MarkKGreenway

Reputation: 8774

Most of the Time for this I use, Json.NET

JsonConvert.SerializeObject(obj); 

Most of the time that does the trick. If need be you can set some JsonSerializerSettings

Upvotes: 8

Related Questions