When I send a request to a service (that I do not own), it may respond either with the JSON data requested, or with an error that looks like this:
{
"error": {
"status": "error message",
"code": "999"
}
}
In both cases the HTTP response code is 200 OK, so I cannot use that to determine whether there is an error or not - I have to deserialize the response to check.
So I have something that looks like this:
bool TryParseResponseToError(string jsonResponse, out Error error)
{
// Check expected error keywords presence
// before try clause to avoid catch performance drawbacks
if (jsonResponse.Contains("error") &&
jsonResponse.Contains("status") &&
jsonResponse.Contains("code"))
{
try
{
error = new JsonSerializer<Error>().DeserializeFromString(jsonResponse);
return true;
}
catch
{
// The JSON response seemed to be an error, but failed to deserialize.
// Or, it may be a successful JSON response: do nothing.
}
}
error = null;
return false;
}
Here, I have an empty catch clause that may be in the standard execution path, which is a bad smell... Well, more than a bad smell: it stinks.
Do you know a better way to "TryParse" the response in order to avoid a catch in the standard execution path ?
[EDIT]
Thanks to Yuval Itzchakov's answer I improved my method like that :
bool TryParseResponse(string jsonResponse, out Error error)
{
// Check expected error keywords presence :
if (!jsonResponse.Contains("error") ||
!jsonResponse.Contains("status") ||
!jsonResponse.Contains("code"))
{
error = null;
return false;
}
// Check json schema :
const string errorJsonSchema =
#"{
'type': 'object',
'properties': {
'error': {'type':'object'},
'status': {'type': 'string'},
'code': {'type': 'string'}
},
'additionalProperties': false
}";
JsonSchema schema = JsonSchema.Parse(errorJsonSchema);
JObject jsonObject = JObject.Parse(jsonResponse);
if (!jsonObject.IsValid(schema))
{
error = null;
return false;
}
// Try to deserialize :
try
{
error = new JsonSerializer<Error>.DeserializeFromString(jsonResponse);
return true;
}
catch
{
// The JSON response seemed to be an error, but failed to deserialize.
// This case should not occur...
error = null;
return false;
}
}
I kept the catch clause... just in case.
#Victor LG's answer using Newtonsoft is close, but it doesn't technically avoid the a catch as the original poster requested. It just moves it elsewhere. Also, though it creates a settings instance to enable catching missing members, those settings aren't passed to the DeserializeObject call so they are actually ignored.
Here's a "catch free" version of his extension method that also includes the missing members flag. The key to avoiding the catch is setting the Error property of the settings object to a lambda which then sets a flag to indicate failure and clears the error so it doesn't cause an exception.
public static bool TryParseJson<T>(this string #this, out T result)
{
bool success = true;
var settings = new JsonSerializerSettings
{
Error = (sender, args) => { success = false; args.ErrorContext.Handled = true; },
MissingMemberHandling = MissingMemberHandling.Error
};
result = JsonConvert.DeserializeObject<T>(#this, settings);
return success;
}
Here's an example to use it:
if(value.TryParseJson(out MyType result))
{
// Do something with result…
}
With Json.NET you can validate your json against a schema:
string schemaJson = #"{
'status': {'type': 'string'},
'error': {'type': 'string'},
'code': {'type': 'string'}
}";
JsonSchema schema = JsonSchema.Parse(schemaJson);
JObject jobj = JObject.Parse(yourJsonHere);
if (jobj.IsValid(schema))
{
// Do stuff
}
And then use that inside a TryParse method.
public static T TryParseJson<T>(this string json, string schema) where T : new()
{
JsonSchema parsedSchema = JsonSchema.Parse(schema);
JObject jObject = JObject.Parse(json);
return jObject.IsValid(parsedSchema) ?
JsonConvert.DeserializeObject<T>(json) : default(T);
}
Then do:
var myType = myJsonString.TryParseJson<AwsomeType>(schema);
Update:
Please note that schema validation is no longer part of the main Newtonsoft.Json package, you'll need to add the Newtonsoft.Json.Schema package.
Update 2:
As noted in the comments, "JSONSchema" have a pricing model, meaning it isn't free. You can find all the information here
A slightly modified version of #Yuval's answer.
static T TryParse<T>(string jsonData) where T : new()
{
JSchemaGenerator generator = new JSchemaGenerator();
JSchema parsedSchema = generator.Generate(typeof(T));
JObject jObject = JObject.Parse(jsonData);
return jObject.IsValid(parsedSchema) ?
JsonConvert.DeserializeObject<T>(jsonData) : default(T);
}
This can be used when you don't have the schema as text readily available for any type.
Just to provide an example of the try/catch approach (it may be useful to somebody).
public static bool TryParseJson<T>(this string obj, out T result)
{
try
{
// Validate missing fields of object
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.MissingMemberHandling = MissingMemberHandling.Error;
result = JsonConvert.DeserializeObject<T>(obj, settings);
return true;
}
catch (Exception)
{
result = default(T);
return false;
}
}
Then, it can be used like this:
var result = default(MyObject);
bool isValidObject = jsonString.TryParseJson<MyObject>(out result);
if(isValidObject)
{
// Do something
}
You may deserialize JSON to a dynamic, and check whether the root element is error. Note that you probably don't have to check for the presence of status and code, like you actually do, unless the server also sends valid non-error responses inside a error node.
Aside that, I don't think you can do better than a try/catch.
What actually stinks is that the server sends an HTTP 200 to indicate an error. try/catch appears simply as checking of inputs.
Add an Error property to your class, or even better use a base class with this error property, like this:
public class BaseResult
{
public Error Error { get; set; }
public bool HasError => String.IsNullOrEmpty(Error?.Code);
}
public class Error
{
public string Status { get; set; }
public string Code { get; set; }
}
Any result class inherits from this base result:
public class MyOkResponseClass : BaseResult
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public int Prop3 { get; set; }
}
Then you can check the property HasError. No exceptions, no extended methods and no weird checks.
To test whether a text is valid JSON regardless of schema, you could also do a check on the number of quotation marks:" in your string response, as shown below :
// Invalid JSON
var responseContent = "asgdg";
// var responseContent = "{ \"ip\" = \"11.161.195.10\" }";
// Valid JSON, uncomment to test these
// var responseContent = "{ \"ip\": \"11.161.195.10\", \"city\": \"York\", \"region\": \"Ontartio\", \"country\": \"IN\", \"loc\": \"-43.7334,79.3329\", \"postal\": \"M1C\", \"org\": \"AS577 Bell Afgh\", \"readme\": \"https://ipinfo.io/missingauth\"}";
// var responseContent = "\"asfasf\"";
// var responseContent = "{}";
int count = 0;
foreach (char c in responseContent)
if (c == '\"') count++; // Escape character needed to display quotation
if (count >= 2 || responseContent == "{}")
{
// Valid Json
try {
JToken parsedJson = JToken.Parse(responseContent);
Console.WriteLine("RESPONSE: Json- " + parsedJson.ToString(Formatting.Indented));
}
catch(Exception ex){
Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);
}
}
else
Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);
Related
I have a simple web API that performs some basic validation using validation attributes. If the request does not meet the validation requirements, I then extract the error messages from the ModelStateDictionary and return these to the caller as a dictionary, where the key is the path to the property that has an error and the message indicates what the problem is.
The problem is that, given certain input's, the error messages in the ModelStateDictionary often contains information that I would not want to return to the client (such as the full name (including namespace) of the object that the converter attempted to map the JSON to).
Is it possible to differentiate between validation errors (generated by Validation Attributes / IValidatableObject implementations) and errors generated by attempting to map invalid JSON to a certain object?
For example:
My models
public class Order
{
[Required]
public Customer Customer { get; set; }
}
public class Customer
{
[Required]
public string Name { get; set; }
}
My action method
public IActionResult Post(Order order)
{
if (!ModelState.IsValid)
{
var errors = GetErrors(ModelState);
return BadRequest(errors);
}
return Ok();
}
private Dictionary<string, string> GetErrors(ModelStateDictionary modelState)
{
var errors = new Dictionary<string, string>();
foreach (var error in modelState)
{
string message = null;
if (error.Value.Errors.Any(e => e.Exception != null))
{
message = "Unable to interpret JSON value.";
}
else
{
message = string.Join(". ", string.Join(". ", error.Value.Errors.Select(e => e.ErrorMessage)));
}
errors.Add(error.Key, message);
}
return errors;
}
My example inputs:
Missing required data
{
"Customer": {
"Name": null // name is required
}
}
Because Customer.Name is decorated with the RequiredAttribute, this generates an error message of:
{
"Customer.Name": "The Name field is required."
}
This is something that's OK to return to the caller, so no issues here.
JSON value doesnt map to .Net object type
{
"Customer": 123 // deserializing will fail to map this value to a Customer object
}
Because deserializing the value 123 to a Customer object fails, this generates an error message of:
{
"Customer": "Error converting value 123 to type 'MyProject.Models.Customer'. Path 'Customer', line 2, position 19."
}
This is not OK to return to the caller, as it contains the full namespace path to the object being mapped to. In this case, something generic (such as "Bad JSON value.") should be used as the error message.
Can anyone help me find a solution to hide these error messages which contain information that should not be returned to the caller?
As can be seen in my code above, I thought I might be able to check the ModelEntry.Exception property and use this to determine whether the error message needs to be shielded from the caller, but this is null in both my examples.
On solution may be to check if the error message starts with Error converting value, and if so, shield the message from the caller. This doesnt seem very robust, and I'm not sure how reliable this will be in a real-world example.
The default response type for HTTP 400 responses is ValidationProblemDetails class. So, we will create a custom class which inherits ValidationProblemDetails class and define our custom error messages.
public class CustomBadRequest : ValidationProblemDetails
{
public CustomBadRequest(ActionContext context)
{
ConstructErrorMessages(context);
Type = context.HttpContext.TraceIdentifier;
}
private void ConstructErrorMessages(ActionContext context)
{
//this is the error message you get...
var myerror = "Error converting value";
foreach (var keyModelStatePair in context.ModelState)
{
var key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
if (errors != null && errors.Count > 0)
{
if (errors.Count == 1)
{
var errorMessage = GetErrorMessage(errors[0]);
if (errorMessage.StartsWith(myerror))
{
Errors.Add(key, new[] { "Cannot deserialize" });
}
else
{
Errors.Add(key, new[] { errorMessage });
}
}
else
{
var errorMessages = new string[errors.Count];
for (var i = 0; i < errors.Count; i++)
{
errorMessages[i] = GetErrorMessage(errors[i]);
if (errorMessages[i] == myerror)
{
errorMessages[i] = "Cannot deserialize";
}
}
Errors.Add(key, errorMessages);
}
}
}
}
string GetErrorMessage(ModelError error)
{
return string.IsNullOrEmpty(error.ErrorMessage) ?
"The input was not valid." :
error.ErrorMessage;
}
}
Configure in your Startup.cs:
services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problems = new CustomBadRequest(context);
return new BadRequestObjectResult(problems);
};
});
Be sure your Controller contains [ApiController] attribute.
Result:
Typically when model binding fails because the JSON payload is not properly formatted, then the incoming model object ("order" in your case) will be null. Just put in a simple null check and return BadRequest with a generic error message.
if (order == null)
return BadRequest("Invalid JSON");
I'm doing C# JSON <-> PHP JSON for the first time.
Thought I'd get on an easy road but seems like I've hit the rock.
I'm fairly sure that JSON from Newtonsoft allows "[" character but not sure why i have this error instead?
Here's my C# code:
public class SystemJSON
{
public bool Status { get; set; }
public string Message { get; set; }
public string ResponseData { get; set; }
}
public static class SystemCall
{
public static String Post(string uri, NameValueCollection pairs)
{
byte[] response = null;
using (WebClient wc = new WebClient())
{
response = wc.UploadValues(uri, pairs);
}
return Encoding.Default.GetString(response);
}
}
string system_Response = SystemCall.Post("http://127.0.0.1:8080/edsa-NEFS%20(PHP)/api.php", new NameValueCollection()
{
{"do_work", Functions.Get_Department_List.ToString()},
{"api_data", null }
});
**SystemJSON systemJSON = JsonConvert.DeserializeObject<SystemJSON>(system_Response);** //<-- Error happens here.
if(systemJSON.Status == true)
{
//do stuff here
}else
{
MessageBox.Show(this, systemJSON.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
And here's my PHP code:
<?php
// Load Request
$function_name = isset($_POST['do_work']) ? $_POST['do_work'] : '';
$api_data = isset($_POST['api_data']) ? $_POST['api_data'] : '';
// Validate Request
if (empty($function_name))
{
SystemResponse(false, 'Invalid Request');
}
if (!function_exists($function_name))
{
SystemResponse(false, 'API Method Not Implemented');
}
// Call API Method
call_user_func($function_name, $api_data);
/* Helper Function */
function SystemResponse($responseStatus, $responseMessage, $responseData = '')
{
exit(json_encode(array(
'Status' => $responseStatus,
'Message' => $responseMessage,
'ResponseData' => $responseData
)));
}
/* API Methods */
function Get_Department_List($api_data)
{
//Test ------------------------------------------START
$node = array();
$dept = array();
$responseData = array();
$dept['id'] = 1;
$dept['name'] = "General";
$dept['description'] = "Forms, Samples, Templates, Catalogs, etc";
$dept['status'] = 1;
array_push($node, $dept);
$dept['id'] = 2;
$dept['name'] = "Test";
$dept['description'] = "Testing";
$dept['status'] = 1;
array_push($node, $dept);
$responseData["dept"] = $dept;
SystemResponse(true, 'SUCCESS', $responseData);
//Test ------------------------------------------END
}
?>
And here's my error:
Newtonsoft.Json.JsonReaderException HResult=0x80131500
Message=Unexpected character encountered while parsing value: {. Path
'ResponseData', line 1, position 51.
The problem is that your C# SystemJSON class does not match the structure of the incoming JSON correctly.
ResponseData in your C# SystemJSON class is listed as a string but your PHP appears to be pushing out a complex object inside that property. You can't deserialise an object into a string - there is no way for the deserialiser to know how to translate the object structure into a suitable string, and anyway it's not generally a useful or logical thing to do. So instead it throws an error to say the object structure doesn't match.
The specific error you're seeing means the deserialiser is expecting a " to denote the start of a string but instead it's seeing { denoting the start of another object.
Why is this happening? Well, your PHP code will produce a JSON response which looks like this:
{
"Status": true,
"Message": "SUCCESS",
"ResponseData": {
"dept": {
"id": 2,
"name": "Test",
"description": "Testing",
"status": 1
}
}
}
Live demo here
As you can see, ResponseData contains an object, which has a "dept" which in turn is another object with four more properties.
To deserialise this properly, your SystemJSON class will need to be altered, and you'll also need two sub-classes to help it out:
public class SystemJSON
{
public bool Status { get; set; }
public string Message { get; set; }
public ResponseData ResponseData { get; set; }
}
public class ResponseData {
public Department dept {get; set; }
}
public class Department {
public string id {get; set; }
public string description {get; set; }
public int status {get; set; }
}
You will now be able to deserialise the JSON correctly. Here is a live demo of the deserialisation.
P.S the [ character appears to be irrelevant here...it's unclear why you referred to that in your question.
P.P.S. From looking at your PHP I'm guessing that you may be intending to return different data structures in ResponseData depending on which parameter was specified for do_work - i.e. depending on which PHP function is called. If so then you'll need to amend your C# accordingly so that it deserialises to a different concrete class depending on which API method it requests. Or you could possibly cheat and specify ResponseData as dynamic, which will then accept any data structure it received, albeit with the caveat that it's now effectively loosely-typed and so you lose certain benefits when compiling the code such as checking for valid usage of property names, data types etc.
I have a webservice that returns json array (JArray) in string format, but I do not understand how to add state value to that operation and get it in the application that consumes the service.
My question is, should I return a json object with a message and inside a json array? Or just an array? hich is more convenient ?
my ws:
public string getList(string strSalary)
{
List<Employee> listJson = null;
JObject jsonResp = "";
JArray array = null;
try
{
listJson = ReportBLL.getInstance.listEmployees(int.Parse(strSalary));
array = JArray.FromObject(listJson);
//set array status ?: ej
//array status = "succes";
}
catch (Exception ex)
{
//set error message
// array status = "error";
//array message = ex.Message or "Not found employees";
}
return JsonConvert.SerializeObject(array);
}
client call (other asp app):
public static List<Employee> listEmployeeClient(string salary)
{
JObject jo = null; //or arrayjson ?
string strData = "";
strData = webService.getList(salary);
jo = JObject.Parse(strData);
//how to evalue status request ?
/* example
if(jo.status == "error") {
throw new Exception(jo.message);
} else {
iterate array inside json object ?
}
*/
}
Is this logic correct ?
You can create a new entity for API Response and use it for all your API responses, You can test it out by the following example.
In server:
Class APIResponse<T>
{
public bool IsError;
public int ErrorCode;
public string ErrorMessage;
public T ReponseData;
}
public string getList(string strSalary)
{
List<Employee> listJson = null;
APIResponse<Employee> responseString = new APIResponse<Employee>();
try
{
listJson = ReportBLL.getInstance.listEmployees(int.Parse(strSalary));
responseString.isError = false;
responseString.data = JArray.FromObject(listJson);
}
catch (Exception ex)
{
responseString.IsError = true;
responseString.ErrorCode = 404; //You can add custom error codes
responseString.ErrorMessage = ex;
}
return JsonConvert.SerializeObject(responseString);
}
In Client:
public static List<Employee> listEmployeeClient(APIResponse<Employee> salary)
{
//You can access the model here
}
There are many options. If you have REST apis you can use the HTTPStatusCodes, for each request, e.g. 200 for OK, 400 bad request, etc.
If you want more fine tuning, you can have a general structure of your response objects, e.g.
responseDto:
status: any code, error or success
message: any error message
data: any expected data
use a Dictionary<string,object> before serializing, and use dynamic to conveniently get the various fields after deserializing. jArray.ToObject<List<Employee>>() will convert JArray object back to the proper type.
Example below:
class Employee
{
public string Name { get; set; }
}
// serializing
var employees = new List<Employee>()
{
new Employee() {Name = "john"},
new Employee() {Name = "alex"},
new Employee() {Name = "susan"},
new Employee() {Name = "bryan"},
};
var dict = new Dictionary<string, object>
{
["employees"] = employees,
["status"] = "error",
["errormessage"] = "Not found employees"
};
var json = JsonConvert.SerializeObject(dict);
// deserializing
dynamic deserialized = JsonConvert.DeserializeObject(json);
string status = deserialized.status;
string errorMessage = deserialized.errormessage;
JArray jArray = deserialized.employees;
List<Employee> deserializedEmployee = jArray.ToObject<List<Employee>>();
I am posting to an API that may return either 1 of the following 2 formats of JSON strings:
{
"MessageType": 6,
"Message": "Unable to SAVE new record. Invalid posted data."
}
or
{
"Model": {
"Id": "1-6Q0RZ9",
...
},
"ResponseResult": {
"MessageType": 10,
"Message": "Successfully saved, Record Id = 1-6Q0RZ9"
}
}
I need to retrieve the results from MessageType and have tried every if condition I can think of to read the results, because the syntax or retrieving the key:value is different for each JSON string, and there are no other flags to trigger one or the other. So the code I used is:
string result = eml.PostData("API/Save", dataJSON.ToString());
var returnresult = new JavaScriptSerializer().Deserialize<dynamic>(result);
try {
var responseresults = returnresult["ResponseResult"];
rr = responseresults["MessageType"];
rrtxt = responseresults["Message"];
} catch (Exception ex) {
rr = returnresult["MessageType"];
rrtxt = returnresult["Message"];
}
Which works great. If there is a valid Db post it returns the second JSON which is parsed correctly by the TRY statement, if not it throws a "key not found" error and parses the returned string in the CATCH statement (the first JSON example). Obviously this is horrible code but I cannot think of another way to do this, and I was wondering if anyone had suggestions? (please?)
Thanx in advance.
How about deserializing the response to an object with all of the properties on each return type and then just checking the values?
public class ReturnObject
{
public YourModel Model {get;set;}
public ResultObject ResponseResult {get;set;}
public int? MessageType {get;set;}
public string Message {get;set;}
}
string result = eml.PostData("API/Save", dataJSON.ToString());
var returnresult = new JavaScriptSerializer().Deserialize<ReturnObject>(result);
{
if(returnresult.MessageType.HasValue)
{
var messageType = returnResult.MessageType.Value;
etc etc.
}
}
I'd like to search all the places like following where the Anonymous types in Controllers is being used as follows.
if(success) {
returnData = JsonConvert.SerializeObject(new { Success = true, Message = "Operation completed successfully" });
}
else {
returnData = JsonConvert.SerializeObject(new { Success = false, Message = "Operation failed" });
}
In above case the returnData is a JsonResult and its used in our Razor views to parse the status of the AJAX requests.
I want to minimize the usage of Anonymous types in such case as this could be maintenance issue as compiler would not raise any warning/errors if any of the line is written as new { Succes = true, Message = "Operation completed successfully"} and it would result into run-time error in the client-side scripts.
Any insights on restricting such situation or detecting such instances would be appreciated.
Why not search in solution/project for this with option "Use Regular Expressions" on?
\bnew\s*{
Just don't use an anonymous type. Create a new concrete type with the data you plan to use:
public class JSONMessage
{
public string Message { get; set; }
public bool Success { get; set; }
}
Then those lines can be changed to:
if(success) {
returnData = JsonConvert.SerializeObject(new JSONMessage(){ Success = true, Message = "Operation completed successfully" });
}
else {
returnData = JsonConvert.SerializeObject(new JSONMessage(){ Success = false, Message = "Operation failed" });
}
How about wrapping up the json call so you can have run time error/assert:
First an extension to detect anonymous from here: Determining whether a Type is an Anonymous Type
public static class TypeExtension {
public static Boolean IsAnonymousType(this Type type) {
var hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
var nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
var isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
return isAnonymousType;
}
}
Then use that it your new method.
public static object JsonSeralize(object obj)
{
Debug.Assert(!obj.getType().IsAnonymousType());
return JsonConvert.SerializeObject(obj);
}
Now you can easily search for places that illegally call JsonConvert.SerializeObject directly.