Making Json.NET + MongoDB Bson play nice together - c#

I am trying to combine some Json.NET json serialization with MongoDB.
I have a structure similar to this:
public class Master {
...props...
public Detail[] Details {get;set;}
}
public class Detail {
...props...
public dynamic Value {get;set;}
}
In this case I want the Value property of Detail to contain dynamic json. There is no schema, I just want to store whatever comes in there.
If I receive this structure via e.g. Asp.NET, the Value property will be deserialized as JObject if the value is anything else than a primitive such as string, bool etc.
Storing that into MongoDB will result in the Value property being saved as a JObject. I would just like to store it as the original json structure of the value prop.
There are ways to go from JObject to BsonDocument. e.g. var doc = BsonDocument.Parse(jobj.ToString());.
But in such case, I would have to traverse my object structure and do this manually.
(My real structure is also more complex)
I'm now thinking there might be some better way to deal with this?
e.g. somehow attach a converter of some sort to the value property so that the Mongo driver knows how to turn this into proper Bson.
What are my options here?
I figure it needs to be done on the Bson serializer end of things somehow?

Yes, you need to explicitly implement conversion between JObject and BsonDocument type. You can implement your own converter and use attributes to avoid traversing your C# type structure. Try:
public class Detail
{
[BsonSerializer(typeof(DynamicSerializer))]
public dynamic Value { get; set; }
}
public class DynamicSerializer : SerializerBase<dynamic>
{
public override dynamic Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var myBSONDoc = BsonDocumentSerializer.Instance.Deserialize(context);
return (dynamic)JObject.Parse(context.ToString());
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, dynamic value)
{
var bson = MongoDB.Bson.BsonDocument.Parse(value.ToString());
BsonDocumentSerializer.Instance.Serialize(context, args, bson);
}
}

Related

Any way to mimic JsonConverter style serializing/deserializing of complex objects with LiteDB?

I am running a spike moving multiple flat json file storage files over to use LiteDB where each file will become a collection.
At the high level this all seems perfectly fine and should map over well, however when I get into the details of it I cannot find any useful examples showing how you can cope with serializing/deserialzing dynamic properties at runtime.
For example lets say I have:
public class SomeTaskData : ITaskData
{
public Guid Id {get;set;}
public string TaskCode {get;set;} // This identifies what task data type it is
// ...
}
public class TaskList
{
public Guid Id {get;set;}
public string Name {get;set;}
public List<ITaskData> TaskData {get;set;} // Concrete Types Only Known At Runtime
}
So the TaskList is what will be put into the collection as we could have hundreds of them, but the problem here is that the List<ITaskData> underlying type will only be known at runtime when serializing.
In the JSON flat file world we work around this with a custom JsonConverter which knows about all the possible ITaskData implementations and their corresponding TaskCode values, which looks something like this:
public class TaskDataConvertor : JsonConverter
{
public static readonly Type DataType = typeof(ITaskData);
public static readonly string JsonCodeFieldName = "TaskCode";
public static readonly Dictionary<string, Type> CachedTypeLookups = new(); // Omitting how its populated
public override bool CanConvert(Type objectType) => objectType == DataType;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{ serializer.Serialize(writer, value); }
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var elementData = JObject.ReadFrom(reader);
var code = elementData[JsonCodeFieldName].ToString(); // Get The Task Code Field
if (!CachedTypeLookups.ContainsKey(code))
{ return null; }
var strongTaskType = CachedTypeLookups[code]; // Get The Runtime Type From The Cache
return elementData.ToObject(strongTaskType);
}
}
This works fine for us in JSON world, but I cant seem to find out how to map this approach over into the LiteDB world. I looked into the undocumented ITypeNameBinder but its unclear how it stores this type name and how its actually used. I also looked into using the BsonMapper custom type registration but that seems to handle it as a BsonValue as if the type should just be converted to a singular value which wont work here (task data could also contain child task data too, but there is no circular references).
Any help would be great as I basically need to solve this problem before I can progress with the spike, and before anyone asks yes I do need to use this TaskCode field to be the way to identify the runtime type to use, I do not want to store fully qualified names (lots of reasons but outside the scope of this questions).

Determining Object Type from JSON

I have an event that gives me a JSON string:
...
public delegate void DataReceivedHandler(string jsonString);
...
public event DataReceivedHandler OnDataReceived = null;
...
if(OnDataReceived != null)
{
OnDataReceived(jsonString);
}
...
That JSON string could be one of 3 different complex objects: LogOnMessage, LogOffMessage, or DataRequest. Each message has a unique set of fields and properties.
How can I determine which object type the JSON string resolves to?
I know I can write a method that iterates through the JProperty.Name of the JObject and find a match by iterating through my collection of objects and their meta-data, but my gut tells me this is a common challenge to be solved so it must be built in to Newtonsoft JSON .NET somewhere that I simply am overlooking or not understanding. It's probably better and faster than my solution would be too...
I was finally able to detect object type by using JObjects and JsonSchemas.
Steps I Took:
Added a Schema property to my message objects that exposed a _schema field. The first time the property is called, it populates _schema with the return value of JsonSchemaGenerator.Generate(object o).
Convert the JSON string into a JObject via the JObject.Parse() static method.
There is an extension method in Newtonsoft.Json.Schema.Extensions that can compare a JObject to a JsonSchema and determine if they match.
Please note: the methods above have been moved to a separate Newtonsoft.Schema library. So my recommendation is to utilize the latest and greatest library.
private Newtonsoft.Json.Schema.JsonSchema _schema;
public static Newtonsoft.Json.Schema.JsonSchema Schema
{
get
{
if (_schema == null)
{
Newtonsoft.Json.Schema.JsonSchemaGenerator generator = new Newtonsoft.Json.Schema.JsonSchemaGenerator();
_schema = generator.Generate(typeof(DataResponse));
}
return _schema;
}
}
...
Newtonsoft.Json.Linq.JObject message = Newtonsoft.Json.Linq.JObject.Parse(json);
if(Newtonsoft.Json.Schema.Extensions.IsValid(message, DataResponse.Schema))
{...}
else if (Newtonsoft.Json.Schema.Extensions.IsValid(message, ServerStatus.Schema))
{...}
...

Accessing properties with a dot in their name

I am trying to deserialize JSON. My root object has a single property "en.pickthall". I am using dynamic type for reading my JSON. I thought I could just do away with "." in the property since its a local JSON file but then there must be some way to access such a property
var result = App_Code.FileIOHelper.ReadFromDefaultFile("ms-appx:///Assets/en.pickthall.json");
dynamic stuff = JsonConvert.DeserializeObject(result);
foreach(var x in stuff.(en.pickthall)) //Tried this intellisense didn't like it
{
}
You could create a root class to deserialize into and use JsonProperty
public class Root
{
// Use the proper type instead of object
[JsonProperty(PropertyName = "en.pickthall")]
public IEnumerable<object> EnPickthall { get; set; }
public Root() { }
}
Used as follows
Root stuff = JsonConvert.DeserializeObject<Root>(result);
foreach(var x in stuff.EnPickthall)
{
}
You could serialize not to dynamic but to JObject and then access your property via
JObject stuff = JsonConvert.DeserializeObject<JObject>(Jsonstring);
var x = stuff.Value<String>("my.property")
C# doesn't have any way of quoting identifiers. If it's not a valid identifier, your only option is reflection.
However, it's possible the object returned by your JSON deserializer changed the identifiers to make them useable in C# - you might want to enumerate all the properties to check if that is the case. A dynamic object with indexers might also be a solution (allowing e.g. stuff["en.pickthall"]).
Another alternative is to change the way the serializer maps properties. For example, Newtonsoft.Jsoft allows you to customize this using a IContractResolver. It's quite easy to replace the . for something more C#-sane in this way.
I know you said you were using a dynamic type for your JSON deserialization, but I just wanted to point out that there is a .NET RESTful client out there that supports this with static model definitions too. For you or for anyone else who happens upon this response when searching for an answer to their problems with dots in property names in C# REST calls.
As of the newly released RestSharp 106.1.0 (and I do mean this version because this support was just added), it can handle renaming properties with a dot in their name via the DeserializeAs attribute. An example being when I call the ElasticSearch API for a _cat call with the following model:
public class CatResponse
{
public string index { get; set; }
...
[DeserializeAs(Name = "docs.count")]
public string docscount { get; set; }
}
And actually get back the docs.count property deserialized into docscount now:
var resource = $"_cat/indices/{indexPattern}?format=json&pretty=true";
var request = new RestRequest(resource, Method.GET);
var response = client.Execute<List<CatResponse>>(request);
This support is out of the box and doesn't need to use the Newtonsoft.Json.JsonSerializer which I have also heard is a possible solution to this problem but which I couldn't get to work.
With a dynamic object and NewtonSoft.Json:
dynamic json = JValue.Parse(result);
int Opens = Convert.ToInt32(json.opens);
int Clicks = Convert.ToInt32(json.clicks);
string State = json.state;

Any library functions to deserialize JSON automatically and manually?

I'm doing some work for a JIRA REST lib. It uses JSON for communication.
Part of the objects I receive in JSON format are know. The others can be of several different formats. So what I have done is created an object with properties that hide a dictionary
public IssueFields Fields { get; set; }
public IssueType IssueType
{
get { return Fields.IssueType; }
set { Fields.IssueType= value; }
}
IssueFields
{
private Dictionary<string, Field> _fields = new Dictionary<string, Field>();
public string IssueType
{
get { return GetFieldByName<IssueType>(IssueTypeFieldName); }
set { SetFieldByName(IssueTypeFieldName, value); }
}
public T GetFieldByName<T>(string fieldName) where T : class
{
return _fields.ContainsKey(fieldName) ? _fields[fieldName] as T: null;
}
public void SetFieldByName(string fieldName, Field field)
{
if (_fields.ContainsKey(fieldName))
{
_fields[fieldName] = field;
}
else
{
_fields.Add(fieldName, field);
}
}
So I have a bunch of classes like that. I can deserialize into them no problem since JavaScriptSerializer (or any other JSON deserializer) just takes the values and puts them into properties of the objects objects automatically. However there are a bunch of unknown fields all starting with "customField_XXXXX".
What I am currently doing is overriding the JavaScriptSerializer and manually putting EVERYTHING into place. Another idea I got from someone else's code was to re-serialize the dictionary inside the JavaScriptConverter override, then deserialize it into the issue, then put everything else in manually from the dictionary, but that adds a lot of overhead and certainly will raise more than a few eyebrows.
public class MyConverter : JavaScriptConverter
{
private static JavaScriptSerializer _javaScriptSerializer;
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
string json = _javaScriptSerializer.Serialize(dictionary);
Issue issue = _javaScriptSerializer.Deserialize<Issue>(json);
// Then add the rest of my objects manually
Is there any way to get the object back with whatever it could serialize AND the dictionary so I can fill in anything it couldn't on my own? I just haven't been able to find anything other than this method....
Thanks!
Json.Net has a built-in "Extension Data" feature that might solve your problem. After deserializing known properties into your class, it automatically handles adding any "extra" object properties to a dictionary. You can read about it on the author's blog. Also check out this answer for some example code.

Deserialization of JSON object by using DataContractJsonSerializer in C#

I'm sure this question has been asked over and over again, but for some reason, I still can't manage to get this to work.
I want to deserialize a JSON object that contains a single member; a string array:
[{"idTercero":"cod_Tercero"}]
This is the class that I'm trying to deserialize into:
[DataContract]
public class rptaOk
{
[DataMember]
public string idTercero { get; set; }
public rptaOk() { }
public rptaOk(string idTercero)
{
this.idTercero = idTercero;
}
}
This is the method that I try to deserialize:
public T Deserialise<T>(string json)
{
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T));
using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
T result = (T)deserializer.ReadObject(stream);
return result;
}
}
And so I try to fill the object:
rptaOk deserializedRpta = deserializarOk(rpta);
But for some reason, this returns ""
MessageBox.Show(deserializedRpta.idTercero);
Without any dependencies outside of the .net framework, you could do it this way
[DataContract(Name="rptaOk")]
public class RptaOk
{
[DataMember(Name="idTercero")]
public string IdTercero { get; set; }
}
[CollectionDataContract(Name="rptaOkList")]
public class RptaOkList : List<RptaOk>{}
var stream = new StreamReader(yourJsonObjectInStreamFormat);
var serializer = new DataContractSerializer(typeof(RptaOkList));
var result = (RptOkList) serializer.ReadObject(stream);
I don't know if your're wiling to change the library that you're using, but I use library "Newtonsoft.Json" to desserialize JSON objects, it's pretty easy to use
[HttpPost]
public void AddSell(string sellList)
{
var sellList = JsonConvert.DeserializeObject<List<Sell>>(sellListJson);
BD.SaveSellList(sellList);
}
As you can see you can deserialize a whole json object list to a List<> fo the type "Sell", an object that i've created... And, of course, you can do that to an array too. I don't know the correct syntax for this, but you can convert this list to an array afterwards
Hope this helps
I think you're making this a lot more difficult than it needs to be. Firstly, your sample json and the class you're trying to deserialize into do not have an array of strings. They have a single property of type string. Secondly, why are you using this class DataContractJsonSerializer? You're not doing anything with it that you can't get from a simple call to json.NET's generic deserialization method. I would remove all of your code except the class definition and replace it with this simple one liner;
rptaOk[] myInstances = JsonConvert.DeserializeObject<rptaOk>(jsonString);
Also, no matter what the structure of your json is, if you have a class to correctly model it that method will correctly deserialize it. If you want to enforce some kind of contract I recommend using json schemas which json.NET also supports. If you use a schema it enforces a rigid contract, if you attempt to deserialize into an object there is something of an implicit contract. I don't know every scenario which will cause it to throw, but if your json is too far from the class definition it will. If your class has properties that don't appear in the json I believe they will just get initialized with the default values for that type.
EDIT: I just noticed your json is actually an array of objects. So you simply need to make the lhs of that assignment an array or rptaOk objects, rather than a singleton.

Categories