Mongo C#Driver:Deserialize BsonArray - c#

I have a document in mongodb that is structured similar to this:
{
"_id":xxxxx,
"business":[{
"subBusiness":[{
"subBusinessName":"Abusiness",
"a":"aaaa"
},{
"subBusinessName":"Bbusiness",
"b":"bbbbb",
"c":"ccccc"
}]
}]
}
how to make a mapping class to serialize this document?
I also have a class defined to represent dimensions (the sub document from above)
class STObject{
[BsonId]
public ObjectId id{get;set;}
[BsonElement("business")]
public List<Business> BusinessList{get;set;}
}
class Business {
[BsonElement("subBusiness")]
public List<SubBusiness> SubBuiness { get; set; }
}
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(CSSubBusiness),typeof(ApproSubBusiness))]
public class SubBusiness {
[BsonElement("subBusinessName")]
public string SubBusinessName{get;set;}
}
public class AsubBusiness:SubBusiness{
[BsonElement("a")]
public string A{get;set;}
}
public class BsubBusiness:SubBusiness{
[BsonElement("b")]
public string B{get;set;}
[BsonElement("c")]
public string C{get;set;}
}
how to query element "b" in class STObject?

In order to deserialize class hierarchy, document should contain type discriminator field, which tells which type of subclass should be instantiated. By default this field has name _t. But if you already have documents with schema as above and can't change it, then you should override discriminator convention which is used by Mongo.
Looks like you can use subBusinessName field as type discriminator for sub business types. In order to do that, you should remove this field from base type:
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(AsubBusiness), typeof(BsubBusiness))] // btw check types
public class SubBusiness
{
}
And you should provide discriminator values for subtypes:
[BsonDiscriminator("Abusiness")] // provide discriminator value here
public class AsubBusiness : SubBusiness
{
[BsonElement("a")]
public string A { get; set; }
}
[BsonDiscriminator("Bbusiness")]
public class BsubBusiness : SubBusiness
{
[BsonElement("b")]
public string B { get; set; }
[BsonElement("c")]
public string C { get; set; }
}
And final step - create custom convention to make mongo look on this discriminator field for instantiating correct sub class type:
public class SubBusinessDiscriminatorConvention : IDiscriminatorConvention
{
public string ElementName
{
get { return "subBusinessName"; }
}
public Type GetActualType(BsonReader bsonReader, Type nominalType)
{
var bookmark = bsonReader.GetBookmark();
bsonReader.ReadStartDocument();
var actualType = nominalType;
if (bsonReader.FindElement(ElementName))
{
var discriminator = (BsonValue)BsonValueSerializer.Instance.Deserialize(bsonReader, typeof(BsonValue), null);
actualType = BsonSerializer.LookupActualType(nominalType, discriminator);
}
bsonReader.ReturnToBookmark(bookmark);
return actualType;
}
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
{
var classMap = BsonClassMap.LookupClassMap(actualType);
return classMap.Discriminator;
}
}
Now set this convention for your base type serialization:
BsonSerializer.RegisterDiscriminatorConvention(typeof(SubBusiness),
new SubBusinessDiscriminatorConvention());
And you can serialize and deserialize documents in your exact format.
UPDATE: Querying:
var collection = test.GetCollection<STObject>("collectionName");
var sto = collection.FindOne(Query.EQ("_id", new ObjectId("xxxxx")));
var businessList = sto.BusinessList.FirstOrDefault();
var bsub = businessList.SubBuiness.OfType<BsubBusiness>().FirstOrDefault();
var b = bsub.B; // returns bbbbb

Related

How to automatically include type information when serializing?

Is it possible to specify that I always want type-information in the json object when serializing a property in an class?
(Ideally with Newtonsoft).
I'm thinking something like this:
public abstract class Value {...}
public class BigValue : Value {...}
public class SmallValue : Value {...}
public class ValueContainer
{
[JsonSetting(TypenameHandling = TypenameHandling.All)] // <--- Something like this?
public Value TheValue { get; set; }
}
I am aware that I could specify this behavior when doing the parsing with a custom converter.
But I want to include the typeinformation every time objects of this type is serialized, without manually having to specify which serialization options to use.
Newtonsoft.Json's JsonPropertyAttribute has TypeNameHandling property which you can set:
public class Root
{
[JsonProperty(TypeNameHandling = TypeNameHandling.All)]
public Base Prop { get; set; }
}
public class Base
{
public int IntProp { get; set; }
}
public class Child:Base
{
}
// Example:
var result = JsonConvert.SerializeObject(new Root
{
Prop = new Child()
});
Console.WriteLine(result); // prints {"Prop":{"$type":"SOAnswers.TestTypeNamehandling+Child, SOAnswers","IntProp":0}}

How to use abstract class and Interface in hiding base class properties?

I have created a base class as "Common" there are many properties such as pageno,pagesize,search,etc which will use in all classes for entire project(must require).
There is other class as "Area" which extends "Common" class.
All properties are automatic get and set.
Here the problem is,
I have created web api.It returned object of Area class.
So here client received all properties of area and common.But I need specific properties to response.
Means I just need two properties of Area i.t AreaId,AreaName
This requirement for retuned data in different format like JSON and XML.I did with linq it gives specific properties Which I need exactly. But It is anonymous type data. Not strongly object.
following sample of my code
public class Common
{
public int CaseNo { get; set; }
public int? RET_ID { get; set; }
public string MSGSTATUS { get; set; }
public string MSG { get; set; }
public int? LoginId { get; set; }
}
public class Area : Common
{
public int AreaId { get; set; }
public string AreaName { get; set; }
public string PinCode{ get; set; }
}
/Web api code/
public IHttpActionResult GetAreaById(int AreaId, int LoginId)
{
try
{
AreaDAL objDal = new AreaDAL();
Area objBo = new Area();
objBo = objDal.EditArea(AreaId, LoginId);
if (objBo != null)
{
/*Not working for xml returned data(work for json).anonymous type data*/
return ResponseMessage(Request.CreateResponse(HttpStatusCode.OK, new Area { AreaId = objBo.AreaId, AreaName = objBo.AreaName }));
/*working for json and xml */
/*But it retuned all properties of Area and common*/
/*Needed as AreaId and AreaName*/
return ResponseMessage(Request.CreateResponse(HttpStatusCode.OK, objBo));
}
}
Introduce an intermediate class (AreaInfo) and pull AreaId and AreaName members up:
public class AreaInfo : Common
{
public int AreaId { get; set; }
public string AreaName { get; set; }
}
public class Area : AreaInfo
{
public string PinCode{ get; set; }
}
//...
public IHttpActionResult GetAreaById(int AreaId, int LoginId)
{
try
{
AreaDAL objDal = new AreaDAL();
Area objBo = new Area();
objBo = objDal.EditArea(AreaId, LoginId);
if (objBo != null)
{
return ResponseMessage(Request.CreateResponse(HttpStatusCode.OK, new AreaInfo { AreaId = objBo.AreaId, AreaName = objBo.AreaName }));
return ResponseMessage(Request.CreateResponse(HttpStatusCode.OK, objBo));
}
}
edit: for hiding fields in the Common base class, you can:
1) Change their access modifier (e.g. to protected)
2) Mark them with attributes to be skipped on serialization like:
[XmlIgnore] for xml serialization
[JsonIgnore] for json serialization
3) Separate the class hierarchies (Common from AreaInfo<-Area) and use composition for when you need
extra fields in Common class.
e.g.
public class Common<T>
where T: class
{
//... common fields here
public T Data {get;}
public Common(T data) => Data = data;
}
...
var area = new Common(new Area(){...});
//area.LoginId;
//area.Data.AreaId;

Returning a generic object without knowing the type?

I'm still fairly new to programming and have been tasked with creating a WebHook consumer that takes in a raw JSON string, parses the JSON into an object, which will be passed into a handler for processing. The JSON is coming in like this:
{
"id":"1",
"created_at":"2017-09-19T20:41:23.093Z",
"type":"person.created",
"object":{
"id":"person1",
"created_at":"2017-09-19T20:41:23.076Z",
"updated_at":"2017-09-19T20:41:23.076Z",
"firstname":"First",
...
}
}
The inner object can be any object so I thought this would be a great opportunity to use generics and built my class as follows:
public class WebHookModel<T> where T : class, new()
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "object")]
public T Object { get; set; }
[JsonIgnore]
public string WebHookAction
{
get
{
return string.IsNullOrEmpty(Type) ? string.Empty : Type.Split('.').Last();
}
}
}
Then created the following interface:
public interface IWebHookModelFactory<T> where T : class, new()
{
WebHookModel<T> GetWebHookModel(string type, string jsonPayload);
}
What I'm failing to understand is how am I supposed to implement the Factory class without knowing what the type is at compile time?
Playing around with the Model a bit, I changed it to an abstract class with an abstract T object so that it could be defined by a derived class.
public abstract class WebHookModel<T> where T : class, new()
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "object")]
public abstract T Object { get; set; }
[JsonIgnore]
public string WebHookAction
{
get
{
return string.IsNullOrEmpty(Type) ? string.Empty : Type.Split('.').Last();
}
}
}
public PersonWebHookModel : WebHookModel<Person>
{
public override Person Object { get; set; }
}
But I still run into the same issue of trying to implement an interface in which I don't know the type at runtime. From what I've found online, this is an example of covariance, but I haven't found any articles that explain how to resolve this issue. Is it best to skip generics and create a massive
case statement?
public interface IWebHookFactory<TModel, TJsonObject>
where TJsonObject : class, new()
where TModel : WebHookModel<TJsonObject>
{
TModel GetWebHookModel(string type, string jsonPayload);
}
I'm a bit partial to using the abstract class approach because it lets me define individual handlers based on which model I'm passing into my Service.
public interface IWebHookService<TModel, TJsonObject>
where TJsonObject : class, new()
where TModel : WebHookModel<TJsonObject>
{
void CompleteAction(TModel webHookModel);
}
public abstract class BaseWebhookService<TModel, TJsonObject> : IWebHookService<TModel, TJsonObject>
where TJsonObject : class, new()
where TModel : WebHookModel<TJsonObject>
{
public void CompleteAction(TModel webHookModel)
{
var self = this.GetType();
var bitWise = System.Reflection.BindingFlags.IgnoreCase
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic;
var methodToCall = self.GetMethod(jsonObject.WebHookAction, bitWise);
methodToCall.Invoke(this, new[] { jsonObject });
}
protected abstract void Created(TModel webHookObject);
protected abstract void Updated(TModel webHookObject);
protected abstract void Destroyed(TModel webHookObject);
}
public class PersonWebHookService : BaseWebHookService<PersonWebHookModel, Person>
{
protected override void Created(PersonWebHookModel webHookModel)
{
throw new NotImplementedException();
}
protected override void Updated(PersonWebHookModel webHookModel)
{
throw new NotImplementedException();
}
protected override void Destroyed(PersonWebHookModel webHookModel)
{
throw new NotImplementedException();
}
}
Key points for the solution:
1. There needs to be some virtual call in there somewhere.
2. Somehow you need to map from your type tag in your JSON payload to your actual C# class.
IE, "person.created"," --> 'Person'.
If you control the serialization format, JSON.Net can inject its own type tag and do this for you. Assuming you can't go that route ...
So you'll need something like a Dictionary to contain the mapping.
Assuming your definitions is like:
abstract class WebhookPayload // Note this base class is not generic!
{
// Common base properties here
public abstract void DoWork();
}
abstract class PersonPayload : WebhookPayload
{
public override void DoWork()
{
// your derived impl here
}
}
And then you can deserialize like:
static Dictionary<string, Type> _map = new Dictionary<string, Type>
{
{ "person.created", typeof(PersonPayload)}
}; // Add more entries here
public static WebhookPayload Deserialize(string json)
{
// 1. only parse once!
var jobj = JObject.Parse(json);
// 2. get the c# type
var strType = jobj["type"].ToString();
Type type;
if (!_map.TryGetValue(strType, out type))
{
// Error! Unrecognized type
}
// 3. Now deserialize
var obj = (WebhookPayload) jobj.ToObject(type);
return obj;
}

How to change class/property name?

For example:
public class Car{
public string color {get; set;}
public int VINCode {get;set;}
}
Now if I call nameof(Car) it returns "Car"
[Name("something")]
public class Car{
[Name("something_else")]
public string color {get; set;}
public int VINCode {get;set;}
}
But how can I get nameof to return the value in the Name attribute rather than the name of the class or method. eg: nameof(Car) == "something" or nameof(Car.color) == "something_else".
the problem:
var modelState = JsonConvert.DeserializeObject<Dictionary<string, List<string>>>(data);
var departmentViewModels = modelState[nameof(DepartmentListView.DepartmentsViewModels)][0];
var departmentTypes = modelState[nameof(DepartmentListView.DepartmentsViewModels)][0];
fixing for that:
var modelState = JsonConvert.DeserializeObject<DepartmentListView>(data);
var departmentViewModels = modelState.DepartmentsViewModels;
var departmentTypes = modelState.DepartmentTypes;
Serialization of this:
public class DepartmentListView
{
public IEnumerable<DepartmentViewModel> DepartmentsViewModels { get; set; }
public IEnumerable<DepartmentType> DepartmentTypes { get; set; }
}
will be:
departmentsViewModel : [], departmentTypes : [] (with lowercase)
I know I can change that lowercase serialization with JsonProperty, but I thought that I will can change the name of class or property...
I'm afraid you cannot do it with nameof.
But if you want to get the value of a CustomAttribute you can try this:
// I assume this is your Name class
public class Name: Attribute
{
public string Data { get; }
public Name(string data) { Data = data; }
}
Then you can
// Will return "something"
var classAttrData = ((Name) typeof(Car).GetCustomAttribute(typeof(Name))).Data;
// Will return "something_else"
var fieldAttrData = ((Name) typeof(Car).GetField(nameof(Car.color)).GetCustomAttribute(typeof(Name))).Data;
It's look like you are asking about your attempted solution rather than your actual problem.
The solution to get new name of class / property:
Create your class with DisplayNameAttribute as follow:
[DisplayName("something")]
public class Car
{
[DisplayName("something_else")]
public string color { get; set; }
public int VINCode { get; set; }
}
To get your class attribute name:
var attributes = typeof(Car) // type declaration
.CustomAttributes.FirstOrDefault() // first item of collection that contains this member's custom attributes like [DisplayName("something")]
?.ConstructorArguments.FirstOrDefault() // first item of structure collecion
.Value; // value as string - "something"
Similarly to the property name, you only need to get its type first
var attribute = typeof(Car).GetProperty(nameof(Car.color)) // ...
instead of
var attribute = typeof(Car) // ...

C# JsonConvertDeserialization returning null values

I am trying to understand why I am getting null values for the following:
Json:
{
"IdentityService": {
"IdentityTtlInSeconds": "90",
"LookupDelayInMillis": "3000"
}
}
Class:
public class IdentityService
{
public string IdentityTtlInSeconds { get; set; }
public string LookupDelayInMillis { get; set; }
}
Called with :
_identityService = JsonConvert.DeserializeObject<IdentityService>(itemAsString);
The class is instantiated but the values for IdentityTtlInSeconds and LookupDelayInMillis are null. I cannot see why they should be
You need one more class - an object which has one property called IdentityService:
public class RootObject
{
public IdentityService IdentityService { get; set; }
}
You need this class because JSON that you have has one property called IdentityService, and this object has two properties, called IdentityTtlInSeconds and LookupDelayInMillis. If you are using a default serializer your classes need to reflect the structure that you have in your JSON string.
And now you can use it to deserialize your string:
var rootObject = JsonConvert.DeserializeObject<RootObject>(itemAsString);
_identityService = rootObject.IdentityService;

Categories