Deserializing from BsonDocument to string and serializing back to BsonDocument - c#

I have a requirement where I need a property that is actually a JSON value from a MongoDB collection that needs to be deserialized into a string. This conversion is throwing a "Cannot deserialize a 'String' from a BsonType 'Document'" exception.
I tried implementing a JSON custom converter, but as the value is being treated as a BsonDocument, it is not helping and I am getting the same exception. I also need it in the original format as I need to cast it back into a BsonDocument down the line. I guess I would need a custom Bson serializer/deserializer.
Incoming sample document from MongoDB collection:
{
"name": "Jane Doe",
"dob": {
"month": "Sep",
"day": 09,
"year": 1987
}
}
Type it is expecting for deserialization:
public class Person
{
public string name { get; set; }
public Dob dob { get; set; }
public class Dob
{
public string month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
}
Type I want it to deserialize into:
public class Person
{
public string name { get; set; }
public string dob { get; set; }
}

To summarize, you have a public-facing string property on your model that contains JSON which you would like to internally serialize to MongoDB by deserializing the JSON string to some intermediate DTO, then serializing the DTO itself to Mongo.
Here are a couple of approaches to solving your problem.
Firstly, you could introduce a private DTO-valued property Dob SerializedDOB { get; set; } into your data model, mark that property with [BsonElement("dob")] to force it to be serialized, then modify dob to be a non-serialized surrogate property that serializes from and to the underlying SerializedDOB within its getter and setter. The following code shows this approach:
public class Person
{
public string name { get; set; }
[BsonIgnore]
public string dob
{
get => BsonExtensionMethods.ToJson(SerializedDOB);
set => SerializedDOB = MyBsonExtensionMethods.FromJson<Dob>(value);
}
[BsonElement("dob")]
Dob SerializedDOB { get; set; }
class Dob // The DTO
{
public string month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
}
The advantage of this approach is that, by making the JSON string a surrogate, the setter automatically ensures that it is well-formed.
Demo fiddle #1 here.
Secondly, you could create a custom SerializerBase<string> for dob that maps the string value to and from the DTO Dob during (de)serialization. The following code shows this approach:
public class Person
{
public string name { get; set; }
[BsonSerializer(typeof(JsonStringAsObjectSerializer<Dob>))]
public string dob { get; set; }
class Dob // The DTO
{
public string month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
}
public class JsonStringAsObjectSerializer<TObject> : SerializerBase<string> where TObject : class
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
{
if (value == null)
{
var bsonWriter = context.Writer;
bsonWriter.WriteNull();
}
else
{
var obj = MyBsonExtensionMethods.FromJson<TObject>(value);
var serializer = BsonSerializer.LookupSerializer(typeof(TObject));
serializer.Serialize(context, obj);
}
}
public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
var serializer = BsonSerializer.LookupSerializer(typeof(TObject));
var obj = (TObject)serializer.Deserialize(context);
return (obj == null ? null : BsonExtensionMethods.ToJson(obj));
}
}
The advantage of this approach is that JsonStringAsObjectSerializer<TObject> can be reused whenever this requirement arises.
Demo fiddle #2 here.
The following extension method is used with both solutions to deserialize the JSON string to a specified type because, confusingly, BsonExtensionMethods has a ToJson() method but no FromJson() method:
public static partial class MyBsonExtensionMethods
{
// Not sure why but BsonExtensionMethods.cs seems to lack methods for deserializing from JSON, so I added some here.
// See https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Bson/BsonExtensionMethods.cs
public static TNominalType FromJson<TNominalType>(
string json,
JsonReaderSettings readerSettings = null,
IBsonSerializer<TNominalType> serializer = null,
Action<BsonDeserializationContext.Builder> configurator = null)
{
return (TNominalType)FromJson(json, typeof(TNominalType), readerSettings, serializer, configurator);
}
public static object FromJson(
string json,
Type nominalType,
JsonReaderSettings readerSettings = null,
IBsonSerializer serializer = null,
Action<BsonDeserializationContext.Builder> configurator = null)
{
if (nominalType == null || json == null)
throw new ArgumentNullException();
serializer = serializer ?? BsonSerializer.LookupSerializer(nominalType);
if (serializer.ValueType != nominalType)
throw new ArgumentException(string.Format("serializer.ValueType {0} != nominalType {1}.", serializer.GetType().FullName, nominalType.FullName), "serializer");
using (var textReader = new StringReader(json))
using (var reader = new JsonReader(textReader, readerSettings ?? JsonReaderSettings.Defaults))
{
var context = BsonDeserializationContext.CreateRoot(reader, configurator);
return serializer.Deserialize(context);
}
}
}

Related

How do I deserialize JSON where there is an array of ane/value properties along with a Location which has three values?

I am trying to deserialize an HTTP Request to a C# POCO class.
The JSON is:
{
"applicationId":"4284f0b0-61f9-4a9d-8894-766f7b9605b5",
"deviceId":"testdevice22",
"messageType":"cloudPropertyChange",
"properties":[
{"name":"CustomerID","value":202},
{"name":"DeviceSerialNumber","value":"devicesa999"},
{"name":"Location","value":{
"alt":0,
"lat":41.29111465188208,
"lon":-80.91897192058899
}}
],
}
The POCO is:
public class CustomEventModel
{
public string applicationId { get; set; }
public string deviceId { get; set; }
public List<PropertyAttribute> properties { get; set; }
}
public class PropertyAttribute
{
public string name { get; set; }
public string value { get; set; }
}
In my Function App I have:
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var propertyChangeData = JsonConvert.DeserializeObject<CustomEventModel>(requestBody);
The Exception Message is: 2022-05-27T23:14:42.141 [Error] Error in CustomEventModel: Unexpected character encountered while parsing value: {. Path 'properties[7].value', line 1,
It is all related to the Location item. How do I solve this?
The value of "Location" is
{
"alt":0,
"lat":41.29111465188208,
"lon":-80.91897192058899
}
this is a complex object while the other "values" are not. They aren't even the same type. One solution is to create a custom deserializer as documented here: https://www.newtonsoft.com/json/help/html/CustomJsonConverterGeneric.htm
Then write the one-way conversion from the various value types with a custom type (e.g. CustomValue)
public class CustomValueConverter : JsonConverter<CustomValue>
{
public override Version ReadJson(JsonReader reader, Type objectType, CustomValue existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = reader.Value;
return new CustomValue(value);
}
}
where your PropertyAttribute class now looks like this:
public class PropertyAttribute
{
public PropertyAttribute() {}
public PropertyAttribute(object value)
{
//handle the various types of input in the constructor
}
public string name { get; set; }
public CustomValue value { get; set; }
}
now you can deserialize using your custom value converter like this:
var thing = JsonConvert.DeserializeObject<CustomEventModel>(json, new CustomValueConverter());
just change a type of a value property from string to object and add Location class
public class PropertyAttribute
{
public string name { get; set; }
private object _value;
public object value
{
get
{
if (_value as JObject !=null)
return ((JObject)_value).ToObject<Location>();
return _value?.ToString();
}
set { _value = value; }
}
}
public class Location
{
public int alt { get; set; }
public double lat { get; set; }
public double lon { get; set; }
}
how to use
var propertyChangeData = JsonConvert.DeserializeObject<CustomEventModel>(requestBody);
Location location = (Location) propertyChangeData.properties.Where(p => p.name=="Location").FirstOrDefault().value;
string DeviceSerialNumber = (string) propertyChangeData.properties.Where(p => p.name=="DeviceSerialNumber").FirstOrDefault().value;

C# - Clone a class into a dynamic [duplicate]

I have a class MyClass. I would like to convert this to a dynamic object so I can add a property.
This is what I had hoped for:
dynamic dto = Factory.Create(id);
dto.newProperty = "123";
I get the error:
WEB.Models.MyClass does not contain a definition for 'newProperty'
Is that not possible?
The following has worked for me in the past:
It allows you to convert any object to an Expando object.
public static dynamic ToDynamic<T>(this T obj)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var currentValue = propertyInfo.GetValue(obj);
expando.Add(propertyInfo.Name, currentValue);
}
return expando as ExpandoObject;
}
Based on: http://geekswithblogs.net/Nettuce/archive/2012/06/02/convert-dynamic-to-type-and-convert-type-to-dynamic.aspx
As my object has JSON specific naming, I came up with this as an alternative:
public static dynamic ToDynamic(this object obj)
{
var json = JsonConvert.SerializeObject(obj);
return JsonConvert.DeserializeObject(json, typeof(ExpandoObject));
}
For me the results worked great:
Model:
public partial class Settings
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("runTime")]
public TimeSpan RunTime { get; set; }
[JsonProperty("retryInterval")]
public TimeSpan RetryInterval { get; set; }
[JsonProperty("retryCutoffTime")]
public TimeSpan RetryCutoffTime { get; set; }
[JsonProperty("cjisUrl")]
public string CjisUrl { get; set; }
[JsonProperty("cjisUserName")]
public string CjisUserName { get; set; }
[JsonIgnore]
public string CjisPassword { get; set; }
[JsonProperty("importDirectory")]
public string ImportDirectory { get; set; }
[JsonProperty("exportDirectory")]
public string ExportDirectory { get; set; }
[JsonProperty("exportFilename")]
public string ExportFilename { get; set; }
[JsonProperty("jMShareDirectory")]
public string JMShareDirectory { get; set; }
[JsonIgnore]
public string Database { get; set; }
}
I used it in this manner:
private static dynamic DynamicSettings(Settings settings)
{
var settingsDyn = settings.ToDynamic();
if (settingsDyn == null)
return settings;
settingsDyn.guid = Guid.NewGuid();
return settingsDyn;
}
And received this as a result:
{
"id": 1,
"runTime": "07:00:00",
"retryInterval": "00:05:00",
"retryCutoffTime": "09:00:00",
"cjisUrl": "xxxxxx",
"cjisUserName": "xxxxx",
"importDirectory": "import",
"exportDirectory": "output",
"exportFilename": "xxxx.xml",
"jMShareDirectory": "xxxxxxxx",
"guid": "210d936e-4b93-43dc-9866-4bbad4abd7e7"
}
I don't know about speed, I mean it is serializing and deserializing, but for my use it has been great. A lot of flexability like hiding properties with JsonIgnore.
Note: xxxxx above is redaction. :)
You cannot add members to class instances on the fly.
But you can use ExpandoObject. Use factory to create new one and initialize it with properties which you have in MyClass:
public static ExpandoObject Create(int id)
{
dynamic obj = new ExpandoObject();
obj.Id = id;
obj.CreatedAt = DateTime.Now;
// etc
return obj;
}
Then you can add new members:
dynamic dto = Factory.Create(id);
dto.newProperty = "123";
You can't add properties to types at runtime. However, there is an exception which is: ExpandoObject. So if you need to add properties at runtime you should use ExpandoObject, other types don't support this.
Just to add up my experience, if you are using JSON.NET, then below might be one of the solution:
var obj....//let obj any object
ExpandoObject expandoObject= JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
Not tested performances etc.. but works.

How Can I Parse YAML Into a Derived Collection Using YamlDotNet?

Using YamlDotNet, I am attempting to deserialize the following YAML:
Collection:
- Type: TypeA
TypeAProperty: value1
- Type: TypeB
TypeBProperty: value2
The Type property is a required property for all objects under Collection. The rest of the properties are dependent on the type.
This is my ideal object model:
public class Document
{
public IEnumerable<IBaseObject> Collection { get; set; }
}
public interface IBaseObject
{
public string Type { get; }
}
public class TypeAClass : IBaseObject
{
public string Type { get; set; }
public string TypeAProperty { get; set; }
}
public class TypeBClass : IBaseObject
{
public string Type { get; set; }
public string TypeBProperty { get; set; }
}
Based on my reading, I think my best bet is to use a custom node deserializer, derived from INodeDeserializer. As a proof of concept, I can do this:
public class MyDeserializer : INodeDeserializer
{
public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object> nestedObjectDeserializer, out object value)
{
if (expectedType == typeof(IBaseObject))
{
Type type = typeof(TypeAClass);
value = nestedObjectDeserializer(parser, type);
return true;
}
value = null;
return false;
}
}
My issue now is how to dynamically determine the Type to choose before calling nestedObjectDeserializer.
When using JSON.Net, I was able to use a CustomCreationConverter, read the sub-JSON into a JObject, determine my type, then create a new JsonReader from the JObject and re-parse the object.
Is there a way I can read, roll-back, then re-read nestedObjectDeserializer?
Is there another object type I can call on nestedObjectDeserializer, then from that read the Type property, finally proceed through normal YamlDotNet parsing of the derived type?
It's not easy. Here is an GitHub issue explaining how to do polymorphic serialization using YamlDotNet.
A simple solution in your case is to to 2-step deserialization. First you deserialize into some intermediary form and than convert it to your models. That's relatively easy as you limit digging in the internals of YamlDotNet:
public class Step1Document
{
public List<Step1Element> Collection { get; set; }
public Document Upcast()
{
return new Document
{
Collection = Collection.Select(m => m.Upcast()).ToList()
};
}
}
public class Step1Element
{
// Fields from TypeA and TypeB
public string Type { get; set; }
public string TypeAProperty { get; set; }
public string TypeBProperty { get; set; }
internal IBaseObject Upcast()
{
if(Type == "TypeA")
{
return new TypeAClass
{
Type = Type,
TypeAProperty = TypeAProperty
};
}
if (Type == "TypeB")
{
return new TypeBClass
{
Type = Type,
TypeBProperty = TypeBProperty
};
}
throw new NotImplementedException(Type);
}
}
And that to deserialize:
var serializer = new DeserializerBuilder().Build();
var document = serializer.Deserialize<Step1Document>(data).Upcast();

How can I serialize and deserialize a type with a string member that contains "raw" JSON, without escaping the JSON in the process

I want to Deserialize JSON into Object but I don't want to Deserialize nested JSON, nested of nested JSON should convert into JSON list (Please check "My expected output" for clear idea) ...
// Suppose I have below JSON data, Here I have nested JSON for "Address" entity
String jsonEmployees =
"{"Employees":
[{"EmpId":1, "EmpName":"ABC", "Address":[{"AddressId":1, "Address":"Something"},{"AddressId":2, "Address":"Anything"}]},
{"EmpId":2, "EmpName":"XYZ", "Address":[{"AddressId":1, "Address":"Something"},{"AddressId":2, "Address":"Anything"}]}]
}"
public class Employee
{
public int EmpId { get; set; }
public string EmpName { get; set; }
// **Note** : I'm not using List<Address> data type for Address, instead of I want list of address in JSON string
public string Address { get; set; }
}
public class RootObject
{
public List<Employee> Employees { get; set; }
}
var Employees = JsonConvert.DeserializeObject<RootObject>(jsonEmployees);
// My expected output
Employees[0].EmpId = 1;
Employees[0].EmpName = "ABC";
Employees[0].Address = "[{"AddressId":1, "Address":"Something"},{"AddressId":2, "Address":"Anything"}]";
Employees[1].EmpId = 2;
Employees[1].EmpName = "XYZ";
Employees[1].Address = "[{"AddressId":1, "Address":"Something"},{"AddressId":2, "Address":"Anything"}]";
Please suggest me the best way to solve this issue ...
Your question is, How can I serialize and deserialize a type with a string member that contains "raw" JSON, without escaping the JSON in the process?
This can be done via a custom JsonConverter that reads and writes raw JSON using JsonWriter.WriteRawValue() and JRaw.Create():
public class RawConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var raw = JRaw.Create(reader);
return raw.ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (string)value;
writer.WriteRawValue(s);
}
}
Then apply it to your type as follows:
public class Employee
{
public int EmpId { get; set; }
public string EmpName { get; set; }
// **Note** : I'm not using List<Address> data type for Address, instead of I want list of address in JSON string
[JsonConverter(typeof(RawConverter))]
public string Address { get; set; }
}
public class RootObject
{
public List<Employee> Employees { get; set; }
}
Sample fiddle.
Note that the raw JSON string must represent valid JSON. If it does not, then the JSON created will be unreadable. If you want to guarantee that the JSON literal is valid, you can keep the JSON in a parsed state internally:
public class Employee
{
public int EmpId { get; set; }
public string EmpName { get; set; }
[JsonProperty("Address")]
JToken AddressToken { get; set; }
[JsonIgnore]
public string Address
{
get
{
if (AddressToken == null)
return null;
return AddressToken.ToString(Formatting.Indented); // Or Formatting.None if you prefer
}
set
{
if (value == null)
AddressToken = null;
else
// Throw an exception if value is not valid JSON.
AddressToken = JToken.Parse(value);
}
}
}
A converter is not needed for this implementation.
I think you will have to rebuild your list of Employees :
RootObject Employees = JsonConvert.DeserializeObject<RootObject>(jsonEmployees);
List<Employee> EmployeesNew = new List<Employee>();
foreach (var item in Employees.Employees)
{
string StringAddress = JsonConvert.SerializeObject(item.Address, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
EmployeesNew.Add(new Employee { EmpId = item.EmpId, EmpName = item.EmpName, AddressString = StringAddress });
}
Your class:
public class Employee
{
public int EmpId { get; set; }
public string EmpName { get; set; }
// **Note** : I'm not using List<Address> data type for Address, instead of I want list of address in JSON string
public List<AddressItems> Address { get; set; }
public string AddressString { get; set; }
}
public class RootObject
{
public List<Employee> Employees { get; set; }
}
public class AddressItems
{
public int AddressId { get; set; }
public string Address { get; set; }
}
In JSON curly brackets {} are the notation for an object, and square brackets [] are for arrays.
As long as your array is encapsulated in the curly brackets like that, you'll have to use another containing object. If you can get rid of the {"Employees": prefix and } suffix, you should be able to convert the JSON to a list of employees directly.
EDIT: I'm sorry, I didn't understand the question.
To do what you want is either really simple, or really difficult, depending on whether you can control the JSON.
If you can control the JSON, all you need to do is add single quotes around the address value so it'll look like this:
[{"EmpId":1, "EmpName":"ABC", "Address":'[{"AddressId":1, "Address":"Something"},{"AddressId":2, "Address":"Anything"}]'},
And that's it! now the serializer will convert it to string.
If you can't than you have to deserialize the JSON by yourself, token by token. Or you can do a workaround, create a corresponding address class, and let the serializer convert to it. Then add a calculated property and use the serializer to deserialize the addresses back to JSON.
(Ugly, I know, but it'll be much faster to implement).

Derserialize JSON Object from Firebase in C#

I am querying Firebase and retrieve a collection of objects like so:
{"-K5f0ccEKkVkxTAavQKY": {
"Appeal": {
"ID": "1450273330435",
"comps": [
162248,
162272,
162273,
162281,
162544
],
"property": {
"Address": "15 Main Street",
"propID": 169729
},
"timeDateStamp": "Wed Dec 16 2015 08:42:10 GMT-0500 (Eastern Standard Time)",
"userUUID": "google:229139952703238437512",
"year": 2016
}
}}
I would like to deserialize them into objects with this definition:
public class Appeal
{
public string ID;
public List<string> comps;
public AppealProperty property;
public string timeDateStamp;
public string UUID;
public int year;
}
public class AppealProperty
{
public string address;
public string propID;
}
I have troubles getting it deserialized. I don't need the initial string (e.g. "K5f0ccEKkVkxTAavQKY"). I'm able to change the object definitions if need be. I have a feeling a Dictionary would be useful.
The quick and dirty object is to use Dictionary<string,Appeal> as your deserialization target. At that point it would be as simple as:
var firebaseLookup = JsonConvert.DeserializeObject<Dictionary<string,Appeal>>(json);
var data = firebaseLookup.Values.ToList(); // or FirstOrDefault();
This approach would also handle the case if you ever had to get multiple objects at once, and it would give you the opportunity to use that key if it turns out the key was important after all.
You could serialise your data into the classes below.
public class AppealProperty
{
public string Address { get; set; }
public int propID { get; set; }
}
public class Appeal
{
public string ID { get; set; }
public List<int> comps { get; set; }
public AppealProperty property { get; set; }
public string timeDateStamp { get; set; }
public string userUUID { get; set; }
public int year { get; set; }
}
public class FireBase
{
public Appeal Appeal { get; set; }
}
public class RootObject
{
[JsonProperty(PropertyName = " - K5f0ccEKkVkxTAavQKY")]
public FireBase FireBaseRoot
{
get;
set;
}
}
Assuming that you are using JSON.NET, you can then get the object you are after, using this snippet:
var firebaseObject = JsonConvert.DeserializeObject<RootObject>(json);
var data = firebaseObject.FireBaseRoot.Appeal;
If the root name is dynamic, as indicated by your comment, you could skip the root instead and serialise straight into the FireBase class:
JObject parsedJson = JObject.Parse(json);
var fireBase = parsedJson.First.Children().First().ToObject(typeof (FireBase));
Since I've never been able to parse a DataSnapshot with newtonSoft Json parser, I did this to build a list of object I needed to put in a ListView:
MyModelObject class
public class MyModelObject: Java.Lang.Object
{
public string Title { get; set; }
public string Description { get; set; }
public MyModelObject(){}
}
into My Listener
public void OnDataChange(DataSnapshot snapshot)
{
List<MyModelObjecct> myList = new List<MyModelObject>();
myList = databaseService
.GetMyModelObjectList(snapshot
.Children?
.ToEnumerable<DataSnapshot>());
}
Method into the DatabaseService class
public List<MyModelObject> GetMyModelObjectList(IEnumerable<DataSnapshot> enumerableSnapshot)
{
List<MyModelObject> list = new List<MyModelObject>();
foreach (var item in enumerableSnapshot)
{
list.Add(ObjectExtensions.DataSnapshotToObject<MyModelObject>(item.Children?.ToEnumerable<DataSnapshot>()));
}
return list;
}
ObjectExtensions class
public static class ObjectExtensions
{
public static T DataSnapshotToObject<T>(IEnumerable<DataSnapshot> source)
where T : class, new()
{
var someObject = new T();
var someObjectType = someObject.GetType();
foreach (var item in source)
{
someObjectType
.GetProperty(item.Key)
.SetValue(someObject, item.Value.ToString(), null);
}
return someObject;
}
}

Categories

Resources