I have APEX classes defined with enum properties that are to be serialized into JSON. Also, I am reading in JSON and deserializing them back to my defined classes.
To make the enum properties work with the JSON transitions, I have created another Integer property that gets the ordinal of the enum and sets the enum based on the enum's values list. See below:
Enum definitions:
public enum DataSourceType {
NA,
ArbitronSummary,
ArbitronTally,
NielsenSummary,
NielsenTally,
Scarborough,
Strata,
None
}
public enum FilterJoinType {
AndJoin,
OrJoin,
NotJoin
}
public enum HourByHourInterval {
NA0,
NA1,
Quarterly,
NA3,
Hourly
}
APEX Class definitions:
public class HourByHourRequest {
public List<String> Books { get; set; }
public DataSourceType eDataSource { get; set; }
public Integer DataSource {
get {
if (eDataSource == null)
return 0;
return eDataSource.ordinal();
}
set {
eDataSource = lib_ap.DataSourceType.values()[value];
}
}
public FilterJoinType eFilterJoinType { get; set; }
public Integer FilterJoinType {
get {
if (eFilterJoinType == null)
return 0;
return eFilterJoinType.ordinal();
}
set {
eFilterJoinType = lib_ap.FilterJoinType.values()[value];
}
}
public HourByHourInterval eInterval { get; set; }
public Integer Interval {
get {
if (eInterval == null)
return 0;
return eInterval.ordinal();
}
set {
eInterval = lib_ap.HourByHourInterval.values()[value];
}
}
}
APEX code using the class to serialize to JSON and deserialize from JSON:
HourByHourRequest request = new HourByHourRequest();
request.Books = new List<String>();
request.Books.add('BookName');
request.eDataSource = DataSourceType.ArbitronTally;
request.eFilterJoinType = FilterJoinType.AndJoin;
request.eInterval = HourByHourInterval.Hourly;
String jsonStr = JSON.serialize(request);
HourByHourRequest request2 = (HourByHourRequest)JSON.deserialize(request, HourByHourRequest.class);
The reason why I used an Integer property to go with each enum property is because upon serializing to JSON the enum value is lost. So having the corresponding Integer value retains the value in JSON, which can be deserialized back successfully... except in the code shown above. The above code will actually fail at the deserialize part due to a "Duplicate field" error for each of the enum/integer field pairs. Both the enum and integer fields are being included in the JSON string when serialized, even though only the integer field is retaining the value.
Sample JSON:
{"Interval":4,
"eInterval":{},
"FilterJoinType":0,
"eFilterJoinType":{},...
My question: Is there a way to ignore fields for serializing to JSON? That would resolve the "Duplicate field" error. Otherwise, how would I go about an appropriate way to handling enums when converting to/from JSON? Thanks!
Got an answer at https://salesforce.stackexchange.com/questions/18498/apex-enum-serialize-to-and-deserialize-from-json.
Basically, you can mark fields as transient to be ignored for serializing to JSON.
Related
I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.
Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}
I have a method being posted to via AJAX with the following header:
public JsonResult GetDocuments(string searchTerm, SortRequest sort)
The SortRequest object is defined as follows:
[DataContract]
public class SortRequest
{
[DataMember(Name = "field")]
public string Field { get; set; }
[DataMember(Name = "dir")]
public string Direction { get; set; }
}
Because of legacy code, the JSON object has the property name "dir" which doesn't directly match the C# property name. We want to use Json.NET as the model binder for JSON requests because it is able to handle this, but the problem is that the JSON coming into the model binder looks like a single object with two top level properties, "searchTerm" and "sort". The deserialization process then tries to map that entire JSON string into each method parameter which obviously fails.
I have tried looking through the now open source .NET MVC code and have not yet been able to determine how the DefaultModelBinder class handles this gracefully. The only option I can see so far is to convert every JSON action to take in a single request parameter but this doesn't seem like a good solution as the DefaultModelBinder doesn't require this.
Edit for clarification:
The JSON request string looks something like this:
{
"searchTerm": "test",
"sort": {
"field": "name",
"dir": "asc"
}
}
We are overriding the DefaultModelBinder and only using Json.NET when the request is of type application/json. Here is the relevant code:
var request = controllerContext.HttpContext.Request;
request.InputStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.InputStream))
{
var jsonString = reader.ReadToEnd();
result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
}
The bindingContext.ModelType is going to be set to String and SortRequest for each parameter in the method, but since the above is a single JSON object, it doesn't map to either of those types and thus inside the method itself, everything is set to default values.
I think the JsonProperty attribute can be used for this as follows:
[DataContract]
public class SortRequest
{
[DataMember(Name = "field")]
[JsonProperty("field")]
public string Field { get; set; }
[DataMember(Name = "dir")]
[JsonProperty("dir")]
public string Direction { get; set; }
}
Update
Based upon the json add a binding prefix:
public JsonResult GetDocuments(string searchTerm, [Bind(Prefix="sort"] SortRequest sort)
I ended up going with a solution using the JToken.Parse method in the Json.NET library. Essentially what is happening is that we check the top level properties of the JSON object and see if there exists the current action parameter we are trying to bind to. Where this falls down is if there is overlap between the parameter name of the action and a property name of a single request being passed in. I think this is enough of an edge case to let slide as it would require only a single object be passed into an action that is expecting multiple.
Here is the modified BindModel method:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result;
if (IsJSONRequest(controllerContext))
{
var request = controllerContext.HttpContext.Request;
request.InputStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.InputStream))
{
var jsonString = reader.ReadToEnd();
// Only parse non-empty requests.
if (!String.IsNullOrWhiteSpace(jsonString))
{
// Parse the JSON into a generic key/value pair object.
var obj = JToken.Parse(jsonString);
// If the string parsed and there is a top level property of the same
// name as the parameter name we are looking for, use that property
// as the JSON object to de-serialize.
if (obj != null && obj.HasValues && obj[bindingContext.ModelName] != null)
{
jsonString = obj[bindingContext.ModelName].ToString();
}
}
result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
}
}
else
{
result = base.BindModel(controllerContext, bindingContext);
}
return result;
}
I have an object with predefined data structure:
public class A
{
public string Id {get;set;}
public bool? Enabled {get;set;}
public int? Age {get;set;}
}
and JSON is supposed to be
{ "Id": "123", "Enabled": true, "Age": 23 }
I want to handle JSON error in positive way, and whenever server returns unexpected values for defined data-types I want it to be ignore and default value is set (null).
Right now when JSON is partially invalid I'm getting JSON reader exception:
{ "Id": "123", "Enabled": "NotABoolValue", "Age": 23 }
And I don't get any object at all.
What I want is to get an object:
new A() { Id = "123", Enabled = null, Age = 23 }
and parsing warning if possible.
Is it possible to accomplish with JSON.NET?
To be able to handle deserialization errors, use the following code:
var a = JsonConvert.DeserializeObject<A>("-- JSON STRING --", new JsonSerializerSettings
{
Error = HandleDeserializationError
});
where HandleDeserializationError is the following method:
public void HandleDeserializationError(object sender, ErrorEventArgs errorArgs)
{
var currentError = errorArgs.ErrorContext.Error.Message;
errorArgs.ErrorContext.Handled = true;
}
The HandleDeserializationError will be called as many times as there are errors in the json string. The properties that are causing the error will not be initialized.
Same thing as Ilija's solution, but a oneliner for the lazy/on a rush (credit goes to him)
var settings = new JsonSerializerSettings { Error = (se, ev) => { ev.ErrorContext.Handled = true; } };
JsonConvert.DeserializeObject<YourType>(yourJsonStringVariable, settings);
Props to Jam for making it even shorter =)
There is another way. for example, if you are using a nuget package which uses newton json and does deseralization and seralization for you. You may have this problem if the package is not handling errors. then you cant use the solution above. you need to handle in object level. here becomes OnErrorAttribute useful. So below code will catch any error for any property, you can even modify within the OnError function and assign default values
public class PersonError
{
private List<string> _roles;
public string Name { get; set; }
public int Age { get; set; }
public List<string> Roles
{
get
{
if (_roles == null)
{
throw new Exception("Roles not loaded!");
}
return _roles;
}
set { _roles = value; }
}
public string Title { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
errorContext.Handled = true;
}
}
see https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm
I have structure IntEx - in short it extends normal Int32 and processed operation. It looks like this:
[Serializable]
public struct IntEx
{
private int internalValue;
private IntEx(int value)
{
internalValue = value;
}
public static implicit operator int(IntEx value)
{
return value.internalValue;
}
public static implicit operator IntEx(int value)
{
return new IntEx(value);
}
}
If we send this structure through WCF it serialize using JSON and output will "nicely look". Like we will use sample code below:
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(TestClass));
using (MemoryStream stream = new MemoryStream())
{
jsonSerializer.WriteObject(stream, testClass);
string serializedString = Encoding.UTF8.GetString(stream.GetBuffer());
Console.WriteLine("JSON: {0}", serializedString);
}
public class TestClass
{
public int I1 { get; set; }
public IntEx I2 { get; set; }
}
Output look like this
JSON: {"I1":11,"I2":{"internalValue":22}}
Client and other "third-part" progam use this format (with internalValue).
Using IntEx is widely use in my application. One of the object is serialized to XML (some kind of setting). This object use IntEx as type.
So I have to implement IXmlSerializable to structure IntEx, because without this property is serialized like empty node
XML: <TestClass><I1>11</I1><I2 /></TestClass>
If I change IntEx to use IXmlSerializable
[Serializable]
public struct IntEx : IXmlSerializable
{
private int internalValue;
private IntEx(int value)
{
internalValue = value;
}
public static implicit operator int(IntEx value)
{
return value.internalValue;
}
public static implicit operator IntEx(int value)
{
return new IntEx(value);
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
throw new NotImplementedException();
}
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteValue(internalValue);
}
}
XML output look ok
XML: <TestClass><I1>11</I1><I2>22</I2></TestClass>
but all my service break down, since now JSON look like this:
JSON: {"I1":11,"I2":"<IntEx xmlns=\"http:\/\/schemas.datacontract.org\/2004\/07\/TestJSONSerialization\">22<\/IntEx>"}
I read that if you use IXmlSerializable, JSON serialization "think" that I'm responsible for serialize so leave this object to me... But how can I change back to "original" serialization.
So now I'm in deadend... I need JSON output look like before, but I also need to some how force to write setting to XML with two conditions:
internalValue should remain private - it shouldn't be accessible using some public Property
I don't want rewrite bunch of code to chaneg (use boxing for JSON's properties) or change all possible property or class that can be saved to XML file.
So can anyone give me some clue, how I can resolve this issue? :/
you can use DataContractJsonSerializer with IDataContractSurrogate. using the IDataContractSurrogate to convert "IntEx" to "IntExJson", and the "IntExJson" don't need to inherit from IXmlSerializable.
IDataContractSurrogate can be used to remove some features from object, and convert to the similar object. and then use:
public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation);
to serialize the object to json. the deserialization is same.
I am having an issue with ASP.Net MVC3 (RC2). I'm finding that the new JSON model binding functionality, which is implicit in MVC3, does not want to deserialize to a property that has an enum type.
Here's a sample class and enum type:
public enum MyEnum { Nothing = 0, SomeValue = 5 }
public class MyClass
{
public MyEnum Value { get; set; }
public string OtherValue { get; set; }
}
Consider the following code, which successfully passes the unit test:
[TestMethod]
public void Test()
{
var jss = new JavaScriptSerializer();
var obj1 = new MyClass { Value = MyEnum.SomeValue };
var json = jss.Serialize(obj1);
var obj2 = jss.Deserialize<MyClass>(json);
Assert.AreEqual(obj1.Value, obj2.Value);
}
If I serialize obj1 above, but then post that data to an MVC3 controller (example below) with a single parameter of type MyClass, any other properties of the object deserialize properly, but any property that is an enum type deserializes to the default (zero) value.
[HttpPost]
public ActionResult TestAction(MyClass data)
{
return Content(data.Value.ToString()); // displays "Nothing"
}
I've downloaded the MVC source code from codeplex but I'm stumped as to where the actual code performing the deserialization occurs, which means I can't work out what the folks at Microsoft have used to perform the deserialization and thus determine if I'm doing something wrong or if there is a workaround.
Any suggestions would be appreciated.
I've found the answer. I hope this is fixed in MVC3 RTM, but essentially what happens is the object deserializes correctly internally via JsonValueProviderFactory, which uses JavaScriptSerializer to do the work. It uses DeserializeObject() so that it can pass the values back to the default model binder. The problem is that the default model binder won't convert/assign an int value when the property type is an enum.
There is a discussion of this at the ASP.Net forums here:
http://forums.asp.net/p/1622895/4180989.aspx
The solution discussed there is to override the default model binder like so:
public class EnumConverterModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
var propertyType = propertyDescriptor.PropertyType;
if(propertyType.IsEnum)
{
var providerValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if(null != providerValue)
{
var value = providerValue.RawValue;
if(null != value)
{
var valueType = value.GetType();
if(!valueType.IsEnum)
{
return Enum.ToObject(propertyType, value);
}
}
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
Then in Application_Start, add the following line:
ModelBinders.Binders.DefaultBinder = new EnumConverterModelBinder();
How are you calling this action? Have you tried:
$.post(
'/TestAction',
JSON.stringify({ OtherValue : 'foo', Value: 5 }),
function(result) {
alert('ok');
}
);