This blog describer How MongoDb ObjectID JSON String Format in Asp .NET Core。
Table of Contents
JSON and BSON
JSON, or JavaScript Object Notation, more commonly known as JSON, was defined as part of the JavaScript language.
BSON simply stands for “Binary JSON,” and that’s exactly what it was invented to be. BSON’s binary structure encodes type and length information, which allows it to be parsed much more quickly.
Issue Description
Here are the Language class. ObjectId is MongoDB.Bson.ObjectId
using System.Collections.Generic;
using Nop.Core.Domain.Stores;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Nop.Core.Domain.Localization
{
/// <summary>
/// Represents a language
/// </summary>
[BsonIgnoreExtraElements]
public partial class Language : BaseEntity, IStoreMappingSupported
{
public Language()
{
Stores = new List<ObjectId>();
}
/// <summary>
/// Gets or sets the name
/// </summary>
public string Name { get; set; } = "English";
/// <summary>
/// Gets or sets the language culture
/// </summary>
public string LanguageCulture { get; set; } = "en-US";
/// <summary>
/// Gets or sets the unique SEO code
/// </summary>
public string UniqueSeoCode { get; set; } = "en";
/// <summary>
/// Gets or sets the flag image file name
/// </summary>
public string FlagImageFileName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the language supports "Right-to-left"
/// </summary>
public bool Rtl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is limited/restricted to certain stores
/// </summary>
public bool LimitedToStores { get; set; }
public IList<ObjectId> Stores { get; set; }
/// <summary>
/// Gets or sets the identifier of the default currency for this language; 0 is set when we use the default currency display order
/// </summary>
public ObjectId DefaultCurrencyId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the language is published
/// </summary>
public bool Published { get; set; } = true;
/// <summary>
/// Gets or sets the display order
/// </summary>
public int DisplayOrder { get; set; } = 1;
}
}
data in mongodb
{
"_id" : ObjectId("5cf6b5c32bac284b687fa506"),
"GenericAttributes" : [ ],
"Name" : "zh",
"LanguageCulture" : "zh",
"UniqueSeoCode" : "zh",
"FlagImageFileName" : "zh.png",
"Rtl" : false,
"LimitedToStores" : false,
"Stores" : [ ],
"DefaultCurrencyId" : ObjectId("5cf6b5c32bac284b687fa506"),
"Published" : true,
"DisplayOrder" : 1
}
Serialization/Deserialization in .NET Core or ASP.NET Core 5.x gives below or similar error:
[2021-02-26T13:06:17.019Z] Information:
WebSocket connected to ws://localhost:51403/_blazor?id=CGMq7Bhkt1jmrG8lQtFnXA.
blazor.server.js:19 [2021-02-26T13:06:29.416Z] Error: System.Text.Json.JsonException:
The JSON value could not be converted to MongoDB.Bson.ObjectId.
Add JsonObjectIdConverter convert ObjectId to string,otherwise it is “00000000000000000000000000”
with Nopcommerce admin web Problem I have a collection of dynamic data. I want to get it back like this:
{ _id: "58b454f20960a1788ef48ebb" ... }
Attempts Here are a list of approaches that do not work:
This await resources = _database.GetCollection<BsonDocument>("resources") .Find(Builders<BsonDocument>.Filter.Empty) .ToListAsync();
return Ok(resources);
Yields [[{"name":"_id","value":{"bsonType":7,"timestamp":1488213234,"machine":614561,"pid":30862,"increment":16027323,"creationTime":"2017-02-27T16:33:54Z","rawValue":{"timestamp":1488213234,"machine":614561,"pid":30862,"increment":16027323,"creationTime":"2017-02-27T16:33:54Z"},"value":{"timestamp":1488213234,"machine":614561,"pid":30862,"increment":16027323,"creationTime":"2017-02-27T16:33:54Z"}}}]]
2.solution
2.1 solution 1
Create Custom ObjectId converter for System.Text.Json
.Net5.x
using System.Text.json solution
System.Text.Json now supports custom type converters in .NET 3.0 and above.
The new System.Text.Json api exposes a JsonConverter api which allows us to convert the type as we like.
Namespace:System.Text.Json.Serialization
public abstract class JsonConverter<T> : System.Text.Json.Serialization.JsonConverter
we can create a ObjectId to string converter.
office doc:How to write custom converters for JSON serialization
Resolution 1
step 1:Write ObjectId converter,Converts an object or value to or from JSON.
We start by implementing the Read and Write method. The Read method will be used for deserialization and the Write method is used when serializing.
Converts an object or value to or from JSON.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson;
using System.Text.Json;
using System.Text.Json.Serialization; //JsonConverter<ObjectId>
namespace Nop.Web.Framework.Json
{
/// <summary>
/// come from:https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0
/// </summary>
public class JsonObjectIdConverterBySystemTextJson:JsonConverter<ObjectId>
{
public override ObjectId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new ObjectId(JsonSerializer.Deserialize<string>(ref reader, options));
// public override void Write(Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Id);
public override void Write(Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}
Note: The above converter converts from ObjectId to String or convert string id to ObjectIds.
You'll still need to register the ObjectIdConverter globally
How to use in Asp .NetCore Web API project(Server)
step 2:Register a custom converter in web api project
Globally use a JsonConverter on a class without the attribute.
Register it in System.Text.json globally with global settings and you not need mark you models with big attributes.
When working with MVC/Razor Page, we can register this converter in startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.Converters.Add(new JsonObjectIdConverterBySystemTextJson());
}
);
Test
http://localhost:5000/api/EmailAccount
// GET: api/<controller>
[HttpGet]
public async Task<ActionResult<IList<EmailAccount>>> Get()
{
var emailAccount = _emailAccountService.GetAllEmailAccounts();
return Ok(emailAccount);
}
output
[
{"Email":"[email protected]",
"DisplayName":"Store name",
"Host":"smtp.mail.com",
"Port":2500,"Username":"123",
"Password":"123",
"EnableSsl":false,
"UseDefaultCredentials":false,
"FriendlyName":"[email protected] (Store name)",
"GenericAttributes":[],
"Id":"5cf6a0da501c97205c1d1da6"
},
{"Email":"[email protected]","DisplayName":"ASP.NET Core Blazor","Host":"","Port":25,"Username":"","Password":"","EnableSsl":false,"UseDefaultCredentials":false,"FriendlyName":"[email protected] (ASP.NET Core Blazor)","GenericAttributes":[],
"Id":"5e9267e739323e2eb4162bb4"
}
]
Making application level changes for JSON serialization
1. Globally configuring values for JSON Serializer in ASP.NET Core 5.x
A few code changes in Startup class is as follows:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new FloatConverter());
});
}
2. Globally configuring values for JSON Serializer in ASP.NET Core Web Api 5.x
public void ConfigureServices(IServiceCollection services)
{
//come from:https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-5.0
services.AddControllers()
.AddJsonOptions(options =>
{
// Use the default property (Pascal) casing.
options.JsonSerializerOptions.PropertyNamingPolicy = null;
// Configure a custom converter.
options.JsonSerializerOptions.Converters.Add(new JsonObjectIdConverterUsingSystemTextJson());
}
);
3.How to use in Asp .NetCore Blazor Server project(client)
step 1: use httpclient get json string from web api
step 2:
step 3:
step 4:How to globally set default options for System.Text.Json.JsonSerializer?
2.2 solution 2
Create Custom ObjectId converter for Newtonsoft.Json
using Newtonsoft.Json
Step 1:Write a JsonObjectIdConverter,it will convert ObjectId to string when object field type is ObjectId
using MongoDB.Bson;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
namespace Nop.Web.Framework.Json
{
/// <summary>
/// question and answer:https://www.cpume.com/question/ffzgitse-mongo-c-sharp-driver-and-objectid-json-string-format-in-net-core.html
/// soucecode come from :https://gist.github.com/cleydson/d1583f87f6fb7e2a8ee67e2455a1bb56
/// </summary>
public class JsonObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value.GetType().IsArray)
{
writer.WriteStartArray();
foreach (var item in (Array)value)
{
serializer.Serialize(writer, item);
}
writer.WriteEndArray();
}
else
serializer.Serialize(writer, value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
var objectIds = new List<ObjectId>();
if (token.Type == JTokenType.Array)
{
foreach (var item in token.ToObject<string[]>())
{
objectIds.Add(new ObjectId(item));
}
return objectIds.ToArray();
}
if (token.ToObject<string>().Equals("MongoDB.Bson.ObjectId[]"))
{
return objectIds.ToArray();
}
else
return new ObjectId(token.ToObject<string>());
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ObjectId));
}
}
}
How to use:
using Newtonsoft.Json;
using MongoDB.Bson;
using Nop.Web.Framework.Json;
namespace Nop.Web.Framework.Mvc.Models
{
/// <summary>
/// Base nopCommerce model
/// </summary>
///
public partial class BaseNopModel
{
[JsonConverter(typeof(JsonObjectIdConverter))]
public ObjectId Id { get; set; }
}
/// <summary>
/// Base nopCommerce entity model
/// </summary>
public partial class BaseNopEntityModel : BaseNopModel
{
}
}
How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
https://stackoverflow.com/questions/16651776/json-net-cast-error-when-serializing-mongo-objectid/37966098#37966098
objectid regex(Regex for MongoDB ObjectID)
var myregexp = /^[0-9a-fA-F]{24}$/;
subject = "112345679065574883030833";
if (subject.match(myregexp)) {
// Successful match
} else {
// Match attempt failed
}
WebApi设置返回Json
public static HttpResponseMessage toJson(Object obj)
{
String str;
if (obj is String || obj is Char)
{
str = obj.ToString();
}
else
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
str = serializer.Serialize(obj);
}
HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(str, Encoding.GetEncoding("UTF-8"), "application/json") };
return result;
}
Useful links:
JsonSerializerOptions source code
office doc:How to write custom converters for JSON serialization
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0
Create Custom Int32Converter for System.Text.Json
https://www.thecodebuzz.com/create-custom-int32converter-for-system-text-json/
Create a StringConverter for System.Text.Json for JSON Serialization
https://www.thecodebuzz.com/system-text-json-create-a-stringconverter-json-serialization/
Custom Dictionary<string, object> JsonConverter for System.Text.Json
https://josef.codes/custom-dictionary-string-object-jsonconverter-for-system-text-json/
Create a PersonConverter for System.Text.Json for JSON Serialization
https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Formatters.Json/2.0.0 https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch/2.0.0 .
JSON and BSON
https://www.mongodb.com/json-and-bson