Populate object with non-primitive property as String from JSON - c#

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.

Related

Web API Serialization of MongoDB BsonValue throwing error

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.

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.

Can I get the attributes on a property from the WriteJson method of a custom JsonConverter?

I want to decorate my classes with custom attributes, and read them when I convert to json using json.net inside a custom JsonConverter. I'll then vary the serialization depending on this custom attribute.
public class MyCustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I want to get any attributes set on the property here.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Int64);
}
}
Another approach would be to specify my custom JsonConverter on the property using an attribute, but I don't want to do this because I want to inject some behaviour into the constructor of my custom JsonConverter by instantiating the converters in the JsonSerializer settings as below.
String json = JsonConvert.SerializeObject(new MyCLass(), new JsonSerializerSettings
{
Converters = new List
{
new MyCustomJsonConverter()
}
});
I can get to the name of the property in the textWriter path. And I can see some interesting hints in the documentation about Metadata, but I can't find a way to do this.
Here's an example decorated class:
public class MyCustomAttribute : Attribute { }
public class MyCLass
{
[MyCustom]
public Int64 MyInt { get; set; }
}
JsonConverters apply to types, not to fields or properties.
Instead of adding an attribute to a property that uses an existing type, consider creating a new type and writing a convert for that instead.
public struct MyCustomType
{
...
}
public class MyClass
{
public MyCustomType {get; set;}
}
Besides - in what other way would you ever want to serialize a raw integer? If the integer represents something, then create a struct or class for that something.
See also: "ValueObject" (Domain Driven Design fundamental concept)
Based on your comment below, an alternate approach would be to forget about JsonConverters and simply expose a secondary property:
public class MyClass
{
[JsonIgnore]
public Int64 MyInt {get; set;}
[JsonProperty("MyInt")]
public Int64 MyEncryptedInt
{
get { return Encrypt(MyInt); }
set { MyInt = Decrypt(value); }
}
}

JSON serialize a custom C# struct to a string, not an Object

I've looked at various questions but I am unsure of how to implement this.
I have a custom struct, which currently has no public properties on it. When it is returned via WebApi (not doing any fancy serialization, just returning the custom struct itself), it is returned as an object {}.
public struct CustomStruct
{
private string myProperty;
...
public override string ToString()
{
return this.myProperty;
}
...
}
The custom struct itself is the type of a property on a parent class, which serializes to:
{ "MyProp1":"value1","MyProp2":"value2","MyCustomStruct":{} }
When I override ToString() on the custom struct I want to output one of the private properties. Can I achieve a similar behaviour when returning the object to JavaScript-land, as a JSON object?
E.g. my private property is a string, called "myProperty", set to "test".
If I added a public property called "MyProperty", I'd get the following output:
{ "MyProp1":"value1","MyProp2":"value2","MyCustomStruct":{ "MyProperty":"test" } }
When what I really want is:
{ "MyProp1":"value1","MyProp2":"value2","MyCustomStruct":"test" }
Hope this makes sense.
Here are the related questions that haven't really helped me much. Would like to avoid using JSON.NET if possible but will go for that if it is the only way:
JSON.Net Struct Serialization Discrepancy
C# custom json serialization
JSON.NET with Custom Serializer to a custom object
JSON serialization of enum as string
JavaScriptSerializer.Deserialize - how to change field names
I faced the same challenge. I solved it by writing a custom JsonConverter that uses the objects ToString method when converting:
public class ToStringConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
And then using it like this as JsonConverter attribute:
[JsonConverter(typeof(ToStringConverter))]
public AnotherObjectWithToString MyObject { get; set; }
I should note that this can only be used for serialization as deserialization would require the ToString result to converted back into a object and that will vary by type.
What I have done for now, is add a second property on the parent CustomClass class...
public string MyCustomStructValue { get { return MyCustomStruct.ToString(); } }
then add the [IgnoreDataMember] attribute to the original property...
[IgnoreDataMember]
public CustomStruct MyCustomStruct { get; set; }
which works fine with the following action:
public IEnumerable<CustomClass> Get()
{
return GetResults();
}

Categories

Resources