Web API Serialization of MongoDB BsonValue throwing error - c#

I have a class that uses MongoDB.Bson.BsonValue as the type for some properties. I can't use anything more specific because I need the property to be able to hold several different types in order to achieve the flexibility required.
So far I have had no problems with this setup between MongoDB and my application, accept when I need to send these objects to the browser from a Web API WebMethod. In this case, the default JSON Serilization, which uses Json.NET behind the scenes, fails to serialize BsonValues to JSON. It gives this error:
Unable to cast object of type 'MongoDB.Bson.BsonString' to type 'MongoDB.Bson.BsonBoolean'.
I think what is going on here is that Json.Net is trying to serialize every property but most of the properties of BsonValue throw exceptions as they expect the user to know what type the value is and only use the properties for its actual type (i.e. AsBoolean, AsString, etc).
After a lot of research, it seems like there are two possible fixes for this but I need some more specific info on how to implement them and which one I should use.
The first is making my own MediaTypeFormatter, which I am reluctant to do because we have a very large application and I want to have as little impact on the rest of it as possible, and the MediaTypeFormatter is a global setting. The second is making a JsonConverter that can handle the BsonValue type, but I have some questions about how to do this.
First off, how do I hijack the behind the scenes serialization to use my custom JsonConverter ONLY for serializing BsonValues?
Second, which method on BsonValue will give me the correct value and format to pass to the JsonWriter in the JsonConverter? I read that ToJson() doesn't necessarily output fully valid Json and the RawValue property is deprecated.
I already have a JsonConverter that I use for deserialization, so I will just enable it for serialization as well and implment the WriteJson method.
I am using the MongoDB C# Driver version 2.5.
The following is my JsonConverter:
public class BsonValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BsonValue) || objectType == typeof(BsonNull));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
BsonValue val = BsonValue.Create(reader.Value);
return val;
}
public override bool CanWrite
{
get { return true; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
BsonValue val = value as BsonValue;
writer.WriteRawValue(val.??? /*What goes here?*/);
}
}
My Webmethod is this:
[WebMethod(true)]
public List<DataPipeline.Model.ReportingSetDefinition> GetAllReportingSetDefinitions()
{
if (!CompanyNameSpace.Web.CompanyPage.GetLoggedInUser().HasValue)
{
throw new Exception(Utilities.CompanyMessage.UNAUTH_ACCESS_NOT_LOGGED_IN);
}
List<DataPipeline.Model.ReportingSetDefinition> defList = (List<DataPipeline.Model.ReportingSetDefinition>)DataSetDefinitionRepository.GetReportingSetDefinitions(CompanyNameSpace.Web.CompanyPage.GetLoggedInCompany().Value);
return defList; //this is where behind the scenes serialization to Json happens.
}
Any help is greatly appreciated!
EDIT: As requested by CodeFuller, here is the model class definition (as well as the class it derives from), and the nested objects that contain the BsonValue and an example of an object that causes the error:
[Serializable]
public abstract class AbstractDataSetDefinition : IMongoDocumentModel
{
[BsonId]
public string _id { get; set; }
public int CompanyID { get; set; }
//The left most data feed, which must be the new data that is being processed. Other data sources are joined on this data feed.
public string BaseFeed { get; set; }
//The source system of the base feed.
public string BaseSourceSystem { get; set; }
//An array of DataFeedJoin objects can pull in data from other data feeds.
public List<DataFeedJoin> Joins { get; set; }
public abstract AbstractDataSet GenerateDataSet(RawData rd, Dictionary<int, RawData> rawDataComponents);
}
[Serializable]
public class ReportingSetDefinition : AbstractDataSetDefinition
{
//The name of the DataSet that will be generated. This is a handle that
//will be used to identify all DataSets generated by this DataSetDefinition.
public string ReportingSetName { get; set; }
public string ReportingSetFamily { get; set; }
//Mapping of which fields and from which data feed will populate the various collections of a DataSet.
public Dictionary<string, AbstractDataFieldDefinition> Dates { get; set; }
public Dictionary<string, AbstractDataFieldDefinition> EmpHierarchy { get; set; }
public Dictionary<string, AbstractDataFieldDefinition> ContactHierarchy { get; set; }
public Dictionary<string, AbstractDataFieldDefinition> Attributes { get; set; }
public Dictionary<string, AbstractDataFieldDefinition> DataFields { get; set; }
}
The actual BsonValue property is nested inside the ReportingSetDefinition inside one of the implementations of AbstractDataFieldDefinition. The relevant classes are below:
[Serializable]
[BsonKnownTypes(typeof(SimpleDataFieldDefinition), typeof(ComplexNumericDataFieldDefinition), typeof(ComplexStringDataFieldDefinition), typeof(ComplexDateDataFieldDefinition), typeof(ConditionalDataFieldDefinition))]
public abstract class AbstractDataFieldDefinition
{
public abstract BsonValue GetFieldValue(Dictionary<int, RawData> RawDataComponents);
public abstract Dictionary<string,string> GetDetails();
}
And finally:
[Serializable]
public class SimpleDataFieldDefinition : AbstractDataFieldDefinition
{
[BsonIgnoreIfNull]
public int? JoinID { get; set; }
[BsonIgnoreIfNull]
public string Field { get; set; }
[BsonIgnoreIfNull]
public BsonValue DefaultValue { get; set; }
[BsonIgnoreIfNull]
public bool? IsDate { get; set; }
public SimpleDataFieldDefinition(){ }
public SimpleDataFieldDefinition(BsonValue defValue, bool isDate)
{
DefaultValue = defValue;
IsDate = isDate;
}
}
Here is an example object in MongoDB that causes the error (I marked the values that are deserialized into BsonValue types in C#:
{
"_id" : "38_Test_DefaultValues",
"CompanyID" : 38,
"BaseFeed" : "TEST",
"BaseSourceSystem" : "Company",
"Joins" : [],
"ReportingSetName" : "Test_DefaultValues",
"ReportingSetFamily" : "Company",
"Dates" : {},
"EmpHierarchy" : {},
"ContactHierarchy" : {},
"Attributes" : {},
"DataFields" : {
"String Value Test" : {
"_t" : "SimpleDataFieldDefinition",
"DefaultValue" : "Hello", //This is a BsonValue in C# object
"IsDate" : false
},
"Number Value Test" : {
"_t" : "SimpleDataFieldDefinition",
"DefaultValue" : NumberLong(100), //This is a BsonValue in C# object
"IsDate" : false
},
"Date Value Test" : {
"_t" : "SimpleDataFieldDefinition",
"DefaultValue" : ISODate("2018-01-23T07:00:00.000Z"), //This is a BsonValue in C# object
"IsDate" : true
},
"Boolean Value Test" : {
"_t" : "SimpleDataFieldDefinition",
"DefaultValue" : true, //This is a BsonValue in C# object
"IsDate" : false
}
}
}
EDIT: I have solved my problem using a workaround, however I don't really consider it a solution to the actual problem. I simply manually serialized my object list and returned a string from the web method, then parsed the Json string in my javascript in the front end. This method causes no errors.
[WebMethod(true)]
public string GetAllReportingSetDefinitions()
{
if (!Company.Web.CompanyPage.GetLoggedInUser().HasValue)
{
throw new Exception(Utilities.CompanyMessgage.UNAUTH_ACCESS_NOT_LOGGED_IN);
}
//System.Web.Http.GlobalConfiguration.Configuration.Formatters.RemoveAt(0);
//System.Web.Http.GlobalConfiguration.Configuration.Formatters.Insert(0, new PipelineUI.Serialization.PipelineToJsonFormatter());
List<DataPipeline.Model.ReportingSetDefinition> defList = (List<DataPipeline.Model.ReportingSetDefinition>)DataSetDefinitionRepository.GetReportingSetDefinitions(Company.Web.CompanyPage.GetLoggedInCompany().Value);
//JsonConverter[] converters = { new AbstractDataFieldDefinitionConverter(), new BsonValueConverter() };
string json = JsonConvert.SerializeObject(defList);
//return defList;
return json;
}
And in my jsvascript I simply do JSON.Parse() on the return value. It's not ideal, but it works. If anyone can solve the original problem that would be great.

Related

Exception when serializing polymorphic c# class with method hiding to json, ASP.NET core API

To get deep into the problem, here is a console app :
class Program
{
static void Main()
{
Console.WriteLine(JsonSerializer.Serialize(
new PolyMorphicClass { Data = new SomeData { N = 2} }));
}
class BaseClass
{
public virtual object Data { get; set; }
}
class PolyMorphicClass : BaseClass
{
public new SomeData Data { get; set; }
}
class SomeData
{
public int N { get; set; }
}
}
This code throw an invalid operation exception with this message :
The JSON property name for 'ConsoleApp_for_test.Program+PolyMorphicClass.Data' collides with another property.
I found that if I initializing BaseClass instead, like below, it works
static void Main()
{
Console.WriteLine(JsonSerializer.Serialize(
new BaseClass { Data = new SomeData { N = 2} }));
}
My actual problem is: in a WebAPI where PolymorphicClass is the response type of a controller action that is being serialized to json, and this same exception happens.
extra note: In the API I use this polymorphic behavior to make the response consistent across endpoints i.e. similar data type.
My questions are : is it ok to use the BaseClass instead of the polymorphicClass like I said above in the context of initializing API response? Is there other solutions to serialize this? Can someone explain why the exception is happening?
You can't change the return type with the new keyword, all it does is hide it and requires the use of the same signature.
You could fix this in a couple of ways.
Using Generics
Replacing object with a generic type would allow for you to define PolyMorphicClass with a specific type for Data, which I believe is similar to what you're trying to do here.
class BaseClass<T>
{
public virtual T Data { get; set; }
}
class PolyMorphicClass : BaseClass<SomeData>
{
}
Provide implementations for the property
Properties are essentially 2 methods (a getter and a setter) and you use some default ones with { get; set; }. These defaults get and set, respectively, a private member underneath the hood.
virtual properties are basically saying "You should override my getter and setter". Just specify an underlying member with the type SomeData to get and set. Here's a basic example.
class BaseClass
{
public virtual object Data { get; set; }
}
class PolyMorphicClass : BaseClass
{
private SomeData data { get; set; }
public override object Data
{
get
{
return data;
}
set
{
data = (SomeData) value;
}
}
}
Note that if you deserialize some JSON that can't be casted to SomeData you'll run into a runtime exception of System.InvalidCastException, so you may want to add some additional type checking in your setter.

Populate object with non-primitive property as String from JSON

I have the following two example classes. Firstly the User class...
public class User : ILoadable
{
public User(string code)
{
this.Load(code);
}
public string Code { get; set; }
public UserGroup Group { get; set; }
}
... and the UserGroup class.
public class UserGroup : ILoadable
{
public UserGroup(string code)
{
this.Load(code);
}
public string Code { get; set; }
public int GroupId { get; set; }
// More properties
}
Then I have a method filling the object with data from a json file which is called from the constructors:
public static void Load(this ILoadable obj, string code)
{
string json = GetJsonFromCode(obj.GetType(), code);
JsonConvert.PopulateObject(json, obj);
}
What I want is not to save the User with its complete UserGroup property data, but only with its code, so it can be reconstructed by passing the code to the UserGroup constructor and getting the whole object from there. For example like this:
{
"UserCode": "Admin",
"Group": "Administrator"
}
I already tried creating a JsonConverter and setting it for the Group property with the following code...
[JsonProperty(ItemConverterType = typeof(StringObjectConverter)]
public UserGroup Group { get; set; }
... and converter:
class StringObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(ILoadable).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return Activator.CreateInstance(objectType, new object[] { (string)reader.Value });
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((ILoadable)value).Code);
}
}
But it does not seem to work, because everytime I try to load above seen json, the following exception is thrown:
Newtonsoft.Json.JsonSerializationException: 'Error converting value "Administrator" to type 'MyProject.UserGroup'. Path 'Group', line 2, position 10.'
Inner Exception
ArgumentException: Could not cast or convert from System.String to MyProject.UserGroup.
I could use a little help here, cause I don't know how get this to work, when even the converter does not change anything.
Rather than [JsonProperty(ItemConverterType = typeof(StringObjectConverter))] you must use [JsonConverter(typeof(StringObjectConverter))]:
public class User : ILoadable
{
public User(string code)
{
this.Load(code);
}
public string Code { get; set; }
[JsonConverter(typeof(StringObjectConverter))]
public UserGroup Group { get; set; }
}
Notes:
JsonConverter Instructs the JsonSerializer to use the specified JsonConverter when serializing the member or class.
You want to specify a converter to use for the UserGroup Group member, so this is the attribute to apply.
JsonPropertyAttribute.ItemConverterType specifies a JsonConverter used when serializing the property's collection items.
You would want to apply this attribute if you have a collection, say a List<UserGroup> Groups, and need to specify a converter for each item. Since you don't have a collection, this is not the correct attribute to use.
(Apparently when an ItemConverterType is applied to a member whose value is serialized as a JSON object, Json.NET applies the converter to every member of that object. This is a bit surprising; I can't find any place this is explicitly documented. This accounts for the exception you are seeing.)
Working .Net fiddle.

Double included object while serializing collection class

I'm trying to serialize class Poll, which looks like that:
class Poll
{
(...) //methods
public AnswersCollection answers { get; set; }
public TagsCollection tags { get; set; }
public string question { get; set; }
}
As you can see, I have "TagsCollection" and "AnswersCollection", which both look pretty similiar, so I'll show only one of them.
class AnswersCollection
{
(...) //methods
public List<Answer> answers { get; set; }
}
And, finnaly, Answer class.
class Answer
{
(...) //methods
public string name { get; set; }
public uint voteQuantity { get; set; }
}
All clases have default public constructor (without parameters) so JSON.NET doesn't have any problems with serialization.
Problem is with AnswersCollection (which is encapsulation), because of it, JSON output looks like that:
{
"answers":{
"answers":[
{
"name":"Foo",
"voteQuantity":45
},
{
"name":"Bar",
"voteQuantity":30
}
]
},
"tags":{
"tags":[
{
"name":"FooTag",
"id":5
},
{
"name":"BarTag",
"id":4
}
]
},
"question":"Question?"
}
As you can see, the problem is with structures like "answers":{ "answers": [(...)] }
Is there option to serialize it to structures like "answers" :[(...)] without second "answers" tag?
I tried to use properties like "isReference" but it didn't worked.
The serialization is actually doing exactly what is expected of it.
I know it will not exactly answer your original question as to whether there is a way to ask Json.Net to do what you want, but your best option here would be to have inheritence instead of composition for your AnswersCollection.
Since the name suggests the class is a collection of answers, why not make it inherit from List<Answer> instead of having a property of this type ?
If you really can't change the structure of your object, you could go the long-hard way, and implement your own JsonConverter to have an absolute control of how your properties are going to be serialized / deserialized.
You would have to apply the JsonConverterAttribute to the Poll.answers property to tell Json.Net to use your custom serializer.
However, I would highly recommend not taking that approach, if you can avoid it.
Off-topic as a side note:
you should consider using a CamelCasePropertyNamesContractResolver to tell your serializer to use CamelCasing when serializing your properties, so you don't have to use camelCasing on your property itself: answers should be spelled Answers if you want to follow common naming practices.
If you don't need to go round-trip with your serialization (in other words, you just need to serialize but not deserialize), then one easy way to get the result you want is to make your collection classes implement IEnumerable<T> like this:
class AnswersCollection : IEnumerable<Answer>
{
public List<Answer> answers { get; set; }
public IEnumerator<Answer> GetEnumerator()
{
return answers != null ? answers.GetEnumerator() : new List<Answer>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
This works because Json.Net automatically treats classes that implement IEnumerable as arrays during serialization.
If you need to deserialize as well, then you'll need to go a bit further and do one of the following:
Add a constructor that accepts an IEnumerable<T>, as suggested by #dbc in the comments below;
Make your class implement ICollection<T>, or
Just make your Collection classes inherit from List<T> as #Fabio Salvalai suggested in his answer.
Another alternative, if you don't want to change your classes, is to implement a custom JsonConverter to handle the custom serialization/deserialization. The converter might look something like this:
class CollectionConverter<TCollection, TItem> : JsonConverter where TCollection : new()
{
private string ListPropertyName { get; set; }
public CollectionConverter(string listPropertyName)
{
ListPropertyName = listPropertyName;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TCollection);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<TItem> list = (List<TItem>)typeof(TCollection).GetProperty(ListPropertyName).GetValue(value, null);
JArray array = JArray.FromObject(list);
array.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
List<TItem> list = array.ToObject<List<TItem>>();
TCollection collection = new TCollection();
typeof(TCollection).GetProperty(ListPropertyName).SetValue(collection, list);
return collection;
}
}
To use the converter, you would need to add [JsonConverter] attributes to the Collection properties in your Poll class like this:
class Poll
{
[JsonConverter(typeof(CollectionConverter<AnswersCollection, Answer>), "answers")]
public AnswersCollection answers { get; set; }
[JsonConverter(typeof(CollectionConverter<TagsCollection, Tag>), "tags")]
public TagsCollection tags { get; set; }
public string question { get; set; }
}
Then just serialize and deserialize as usual.

How to selectively exclude a property from serialization based on the parent class type

I'm using the Newtonsoft.JSON library to serialize several objects. In some cases I don't want to serialize a property so I've used the ShouldSerialize prefix which has been largely successful in most cases. In one case I only want to serialize a property if it belongs to a specific class.
I've tried using the stack trace but it only tells me that the JSON object is calling the ShouldSerialize method. I don't need to know what calls ShouldSerialize, I need to know what parent class ShouldSerialize belongs to such as Parent.Child.ShouldSerialize.
How can I determine what the parent class name is while using the JSON object using the code sample below?
class Foo
{
public SharedClass SomeProperty
{
get;
set;
}
}
class Bar
{
public SharedClass SomeProperty
{
get;
set;
}
}
class SharedClass
{
public string SomeValue
{
get;
set;
}
public bool ShouldSerializeSomeValue
{
//pseudo logic
return ClassName == "Foo";
}
}
As was pointed out by Lasse Karlsen in the comments, if your SharedClass does not have a reference to its parent, there is no way for the ShouldSerializeSomeValue() method in that class to know what the parent class is.
However, if you are using Json.Net 6.0 Release 6 or later, you can work around this by using a custom JsonConverter as a means to selectively omit properties from the shared class (instead of using a ShouldSerialize() method), and then place [JsonConverter] attributes on the SharedClass properties within the appropriate parent classes to indicate which properties should be omitted for that instance.
Here is how the updated example class definitions might look. You'll notice I've marked the SharedClass instance on Foo to indicate it should use a custom converter called OmitPropertiesConverter to omit the SomeValue property. The SharedClass instance on Bar does not use a converter, so that instance will be serialized as normal.
class Foo
{
[JsonConverter(typeof(OmitPropertiesConverter), "SomeValue")]
public SharedClass Shared { get; set; }
}
class Bar
{
public SharedClass Shared { get; set; }
}
class SharedClass
{
public string SomeValue { get; set; }
public string SomeOtherValue { get; set; }
}
Below is the code for the OmitPropertiesConverter. Its constructor accepts a propsToOmit string which is a comma-delimited list of property names to be excluded from the serialization. This gets split into an array for later use in the WriteJson method. The WriteJson method takes the SharedClass value, converts it to a JObject, then programmatically removes the properties which are in the propsToOmit array before writing the JObject to the JsonWriter.
class OmitPropertiesConverter : JsonConverter
{
string[] propsToOmit;
public OmitPropertiesConverter(string propsToOmit)
{
this.propsToOmit = propsToOmit.Split(new char[] {','},
StringSplitOptions.RemoveEmptyEntries);
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(SharedClass));
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
JObject jo = JObject.FromObject(value, serializer);
// Note: ToList() is needed here to prevent "collection was modified" error
foreach (JProperty prop in jo.Properties()
.Where(p => propsToOmit.Contains(p.Name))
.ToList())
{
prop.Remove();
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a simple demo program which shows the converter in action:
class Program
{
static void Main(string[] args)
{
var root = new
{
Foo = new Foo
{
Shared = new SharedClass
{
SomeValue = "foo1",
SomeOtherValue = "foo2"
}
},
Bar = new Bar
{
Shared = new SharedClass
{
SomeValue = "bar1",
SomeOtherValue = "bar2"
}
}
};
string json = JsonConvert.SerializeObject(root, Formatting.Indented);
Console.WriteLine(json);
}
}
And here is the output of the above demo. You'll notice that the SomeValue property on the SharedClass instance inside Foo is not included in the output, but it is included on the instance inside Bar.
{
"Foo": {
"Shared": {
"SomeOtherValue": "foo2"
}
},
"Bar": {
"Shared": {
"SomeValue": "bar1",
"SomeOtherValue": "bar2"
}
}
}

Is it possible to serialize an object differently to deserializing it using Json.Net?

Here is my example:
JSON request string:
{
entity: '09f7cb28-0464-41c8-a20d-1b05eb1cda0a'
}
My request object:
public class Request {
public Request() { }
[JsonProperty("entity")]
private string EntityIdentifier { get; set; }
public EntityObject Entity { get; set; }
}
I've got this to work so that the string is passed to the EntityIdentifier, which is fine, then in my code I find the actual entity using the entity identifier property, populate the Entity property with the found entity but then this is what I get when I serialize the object:
{
entity: '09f7cb28-0464-41c8-a20d-1b05eb1cda0a',
Entity: {
// my object's properties
}
}
When all I really want is:
{
entity: {
// my object's properties
}
}
Now, I know I could split this out into two different classes, and I may have to, but if there is a way to keep it all in the same class, it would be awesome and save me a lot of coding time.
To clarify because I seem to not be explaining what I want very well:
Disclaimer: This doesn't exist (AFAIK) but this is what I would like:
public class Request {
public Request() { }
[JsonProperty("entity", OnlyWhen=Deserializing)]
private string EntityIdentifier { get; set; }
[JsonProperty("entity", OnlyWhen=Serializing)]
public EntityObject Entity { get; set; }
}
This is what I would really like to achieve, but as far as I can see the only place I could realistically put this type of code is in a custom converter, but unfortunately, I can't seem to be able to determine whether the converter is being used for serialization or deserialization when it is being used.
How about using the JsonIgnore attribute?
public class Request
{
public Request() { }
[JsonIgnore]
private string EntityIdentifier { get; set; }
[JsonProperty("entity")]
public EntityObject Entity { get; set; }
}
I believe you just need to mark Entity with [ScriptIgnore] and/or [JsonIgnore], like this:
[ScriptIgnore]
[JsonIgnore]
public EntityObject Entity { get; set; }
I have heard of JsonIgnore not working sometimes. Using both is probably your best bet.
Also - I believe your wording is incorrect when describing the problem. You state: "this is what I get when I deserialize the object" - when in fact, I believe you mean to say "serialize".
If you need to populate Entity when EntityIdentifier is set, then replace the code for EntityIdentifier with something like:
string _eId;
[JsonProperty("entity")]
private string EntityIdentifier
{
get{return _eId;}
set
{
_eId = value;
Entity = someMethodToRetrieveTheEntityById(_eId);
}
}
The problem here could be that you're trying to force one class do the job of two. Why not do:
// Deserialize requests into this.
public class EntityRequest
{
[JsonProperty("entity")]
private string EntityIdentifier { get; set; }
}
// Serialize these to file/etc.
public class EntityData
{
[JsonProperty("entity")]
public EntityObject Entity { get; set; }
}
Then you deserialize requests into EntityRequest objects, load EntityData objects using the EntityRequest and some other logic, then serialize the EntityData objects to file. The JsonProperty attributes here mean that both the request and the output entity are both called 'entity' just like in your OP. This seems to be the workflow you're after.
Cramming everything into one class is making this problem more complicated than it needs to be.
You can solve this problem with a custom JsonConverter similar to this:
public class RequestConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Request));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// On deserialization, the JSON has an entity identifier (GUID)
// so use it to retrieve the actual object from the database.
JToken token = JToken.Load(reader);
Request req = new Request();
req.Entity = RetrieveFromDatabase(token["entity"].ToString());
return req;
}
private EntityObject RetrieveFromDatabase(string entityIdentifier)
{
// Implement this method to retrieve the actual EntityObject from the DB.
// (Return null if the object is not found.)
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// On serialization, write out just the entity object properties
Request req = (Request)value;
JObject obj = new JObject();
obj.Add("entity", JToken.FromObject(req.Entity));
obj.WriteTo(writer);
}
}
To use the converter, simply decorate your Request class with a [JsonConverter] attribute as shown below. Note that you can remove the private EntityIdentifier property, as it will no longer be needed. The converter has responsibility for retrieving the EntityObject from the database based on the identifier in the JSON.
[JsonConverter(typeof(RequestConverter))]
public class Request
{
public Request() { }
public EntityObject Entity { get; set; }
}
Note, if you don't want the converter to have responsibility for populating the entity object, you can still use the converter idea. In that case, make the ReadJson method set the EntityIdentifier property on the request. However, since you have made this property private, you will either need to use reflection to do this, make the property public, or make a constructor for the Request that the converter can use to instantiate it with the identifier.

Categories

Resources