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
Related
It seems there exists a difference in the way of deserialization process of Newtonsoft.Json.6.0.8 vs Newtonsoft.Json.12.0.3.
The following is model in our C# project :
public class WebServiceConfigModel
{
public string RestoreFile { get; set; }
public string RestoreFileDescription { get; set; }
}
The action method defined in the controller is as follows:
[HttpPost]
public void Restore(WebServiceConfigModel request)
{
}
The input JSON text which was provided to the method is as follows:
{
"RestoreFile": "SampleFile",
"RestoreFileDescription": {
"ID": "DatasetDescription",
"Label": "Description"
}
}
This was deserialized successfully (the request object contains values), even if there exists a deserialization error and we were able to read the RestoreFile property value in the C# while using the Newtonsoft.Json.6.0.8.
After upgrading the version Newtonsoft.Json to 12.0.3, the request object in C# seems to be null and the deserialization error still exists. It works properly if we change the "RestoreFileDescription" property to a string value.
Is there any way to get the deserialized object even if some of the property has a contract mismatch?
These docs may be helpful.
It appears that as of 12.0.1, you can handle Json Deserialization errors in two ways:
JsonSerializerSettings.Error event
The [OnError] attribute
In the first instance, the JsonSerializerSettings have been set so as to handle a non-date string, and this is handled as per their docs:
The event handler has logged these messages and Json.NET has continued on deserializing the JSON because the errors were marked as handled.
List<DateTime> c = JsonConvert.DeserializeObject<List<DateTime>>(#"[
'2009-09-09T00:00:00Z',
'I am not a date and will error!',
[
1
],
'1977-02-20T00:00:00Z',
null,
'2000-12-01T00:00:00Z'
]",
new JsonSerializerSettings
{
Error = delegate(object sender, ErrorEventArgs args)
{
errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
},
Converters = { new IsoDateTimeConverter() }
});
The second way is to create a method, and decorate it with the [OnError] attribute as follows:
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
errorContext.Handled = true;
}
PersonError person = new PersonError
{
Name = "George Michael Bluth",
Age = 16,
Roles = null,
Title = "Mister Manager"
};
So when Roles is required, this provides the following result:
string json = JsonConvert.SerializeObject(person, Formatting.Indented);
Console.WriteLine(json);
//{
// "Name": "George Michael Bluth",
// "Age": 16,
// "Title": "Mister Manager"
//}
I am using Newtonsoft to deserialize data from a file. When I deserialize two different instances from two different sets of data, both instances' property ends up having the same value. I have created a small project to repro the issue. Here are my 2 JSON files
File1.json:
{
"Name": "File1",
"SomeProperty":
{
"Value": 1
}
}
File2.json:
{
"Name": "File2",
"SomeProperty":
{
"Value": 2
}
}
SomeProperty.cs
namespace Json
{
public class SomePropertyDto
{
public static SomePropertyDto Default = new SomePropertyDto
{
Value = 0
};
public int Value { get; set; }
}
}
FileDataDto.cs
namespace Json
{
public class FileDataDto
{
public string Name { get; set; }
public SomePropertyDto SomeProperty
{
get => someProperty;
set => someProperty = value;
}
private SomePropertyDto someProperty = SomePropertyDto.Default;
}
}
Program.cs
using System.IO;
using Newtonsoft.Json;
namespace Json
{
class Program
{
static void Main(string[] args)
{
string json1 = File.ReadAllText("File1.json");
string json2 = File.ReadAllText("File2.json");
FileDataDto fileData1 = JsonConvert.DeserializeObject<FileDataDto>(json1);
FileDataDto fileData2 = JsonConvert.DeserializeObject<FileDataDto>(json2);
}
}
}
After deserializing both instances of FileDataDto, both their SomeProperty values are the same. However if I do not initialise the FileDataDto someProperty field to SomePropertyDto.Default,
private SomePropertyDto someProperty;// = SomePropertyDto.Default;
it works correctly. If I include the initialisation to the default value
private SomePropertyDto someProperty = SomePropertyDto.Default;
after deserializing fileData1, the SomeProperty value equals 1 as expected. However, after deserializing fileData2, both fileData1 and FileData2 instances' SomeProperty value equals 2 which is not what is expected.
According to https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonSerializerSettings.cs#L46, the default object creation setting is "Auto", which means https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/ObjectCreationHandling.cs#L34
Reuse existing objects, create new objects when needed.
So when your Default object is there, someProperty stay this, the same, shared object for all FileDataDto instances.
Provide customized JsonSerializerSettings (with ObjectCreationHandling set to Replace) if you need that Default value.
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 need to replace the DateTime serialization for JSON in WCF REST Self Hosted service. Right now, I'm using something like the following code to do it, but it's definitely not the way to go since it requires manipulating each class.
[DataContract]
public class Test
{
[IgnoreDataMember]
public DateTime StartDate;
[DataMember(Name = "StartDate")]
public string StartDateStr
{
get { return DateUtil.DateToStr(StartDate); }
set { StartDate = DateTime.Parse(value); }
}
}
where my utility function DateUtil.DateToStr does all the formatting work.
Is there any easy way to do it without having to touch the attributes on my classes which have the DataContract attribute? Ideally, there would be no attributes, but a couple of lines of code in my configuration to replace the serializer with one where I've overridden DateTime serialization.
Everything that I've found looks like I have to replace huge pieces of the pipeline.
This article doesn't appear to apply because in I'm using WebServiceHost not HttpServiceHost, which not part of the 4.5.1 Framework.
JSON.NET Serializer for WCF REST Services
By default WCF uses DataContractJsonSerializer to serialize data into JSON. Unfortunatelly date from this serializer is in very difficult format to parse by human brain.
"DateTime": "\/Date(1535481994306+0200)\/"
To override this behavior we need to write custom IDispatchMessageFormatter. This class will receive all data which should be returned to requester and change it according to our needs.
To make it happen to the operations in the endpoint add custom formatter - ClientJsonDateFormatter:
ServiceHost host=new ServiceHost(typeof(CustomService));
host.AddServiceEndpoint(typeof(ICustomContract), new WebHttpBinding(), Consts.WebHttpAddress);
foreach (var endpoint in host.Description.Endpoints)
{
if (endpoint.Address.Uri.Scheme.StartsWith("http"))
{
foreach (var operation in endpoint.Contract.Operations)
{
operation.OperationBehaviors.Add(new ClientJsonDateFormatter());
}
endpoint.Behaviors.Add(new WebHttpBehavior());
}
}
ClientJsonDateFormatter is simple class which just applies formatter ClientJsonDateFormatter
public class ClientJsonDateFormatter : IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new ResponseJsonFormatter(operationDescription);
}
public void Validate(OperationDescription operationDescription) { }
}
In the formatter we took imput and serialize it with the changed Serializer:
public class ResponseJsonFormatter : IDispatchMessageFormatter
{
OperationDescription Operation;
public ResponseJsonFormatter(OperationDescription operation)
{
this.Operation = operation;
}
public void DeserializeRequest(Message message, object[] parameters)
{
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
string json=Newtonsoft.Json.JsonConvert.SerializeObject(result);
byte[] bytes = Encoding.UTF8.GetBytes(json);
Message replyMessage = Message.CreateMessage(messageVersion, Operation.Messages[1].Action, new RawDataWriter(bytes));
replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
return replyMessage;
}
}
And to send information to client we need data writer - RawDataWriter. Its implementation is simple:
class RawDataWriter : BodyWriter
{
byte[] data;
public RawDataWriter(byte[] data)
: base(true)
{
this.data = data;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Binary");
writer.WriteBase64(data, 0, data.Length);
writer.WriteEndElement();
}
}
Applying all code will result in returning date in more friendly format:
"DateTime":"2018-08-28T20:56:48.6411976+02:00"
To show it in practice I created example in the github branch DateTimeFormatter.
Please check also this answer as very likely you also will need it.
There is a limitation in JSON to convert DateTime, specially according to your case.
Please see http://msdn.microsoft.com/en-us/library/bb412170(v=vs.110).aspx
and read the section Dates/Times and JSON
To resolve this problem, I simply changed the type of serialization from JSON to XML for all the calls including DateTime.
After long time discussion ,I have find out the solution for it.
Please Use the following Code to Solve serialized date..
[IgnoreDataMember]
public DateTime? PerformanceDate { get; set; }
[DataMember(EmitDefaultValue = false, Name = "PerformanceDate")]
public string UpdateStartDateStr
{
get
{
if (this.PerformanceDate.HasValue)
return this.PerformanceDate.Value.ToUniversalTime().ToString("s", CultureInfo.InvariantCulture);
else
return null;
}
set
{
// should implement this...
}
}
There are quite a few questions around JSON deserialization but a lot of them seem to be for MVC 1 or MVC 2. I don't seem to have found a satisfactory answer to this specifically for MVC 3.
I have an object with immutable properties and no default constructor, which I want to deserialize to in an ASP.NET MVC 3 application. Here is a simplified version:
public class EmailAddress
{
public EmailAddress(string nameAndEmailAddress)
{
Name = parseNameFromNameAndAddress(nameAndEmailAddress);
Address = parseAddressFromNameAndAddress(nameAndEmailAddress);
}
public EmailAddress(string name, string address)
{
Guard.Against<FormatException>(!isNameValid(name), "Value is invalid for EmailAddress.Name: [{0}]", name);
Guard.Against<FormatException>(!isAddressValid(address), "Value is invalid for EmailAddress.Address: [{0}]", address);
Name = name;
Address = address;
}
public string Address { get; private set; }
public string Name { get; private set; }
// Other stuff
}
An example controller action might be:
[HttpPost]
public ActionResult ShowSomething(EmailAddress emailAddress)
{
return View(emailAddress)
}
The JSON coming in is:
{"Address":"joe#bloggs.com","Name":"Joe Bloggs"}
What is the best way to get this to deserialize in MVC3? Is there some way of implementing a custom model binder or deserializer class that can handle this?
A solution that doesn't interfere with the object itself would be preferable (ie. a separate deserializer class, rather than adding attributes to properties, etc), although open to any good suggestions.
I found a similar question (with no answer) here: Can I deserialize to an immutable object using JavascriptSerializer?
Is there some way of implementing a custom model binder or
deserializer class that can handle this?
Yes, you could write a custom model binder:
public class EmailAddressModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var addressKey = "Address";
var nameKey = "Name";
if (!string.IsNullOrEmpty(bindingContext.ModelName))
{
addressKey = bindingContext.ModelName + "." + addressKey;
nameKey = bindingContext.ModelName + "." + nameKey;
}
var addressValue = bindingContext.ValueProvider.GetValue(addressKey);
var nameValue = bindingContext.ValueProvider.GetValue(nameKey);
if (addressValue == null || nameValue == null)
{
throw new Exception("You must supply an address and name");
}
return new EmailAddress(nameValue.AttemptedValue, addressValue.AttemptedValue);
}
}
which will be registered in Application_Start:
ModelBinders.Binders.Add(typeof(EmailAddress), new EmailAddressModelBinder());
and finally all that's left is to invoke the action:
$.ajax({
url: '#Url.Action("ShowSomething")',
type: 'POST',
data: JSON.stringify({ "Address": "joe#bloggs.com", "Name": "Joe Bloggs" }),
contentType: 'application/json',
succes: function (result) {
alert('success');
}
});
EDITED ANSWER:
I misread the code, looked at the constructor parameters, instead of the properties.
The cause of your problem is the private set of the properties.
Ie, it should be:
public string Address { get; set; }
public string Name { get; set; }
If you make that change, it should all work.
Just remember:
The model binder looks for PROPERTIES, not the constructor!