I have a .net core WebApi and swagger to test the API. Now my problem is when I specify numbers for a boolean field in my JSON data for a POST request, in my controller method it is treating as True, which I don't want
Here is my model:
{
public bool Field1 { get; set; }
public bool Field2 { get; set; }
}
{
field1: 2
field2: true
}
And this is how I see the values in the controller
{
field1: true
field2: true
}
Here I don't want integer 2 for field1 to be treated as true and instead, the request should fail
Any recommendations or suggestions will be appreciated.
If you want the request to fail, you will have create a custom set method for the boolean properties and throw an exception as per the failure observed. Something like below:
private bool _field1;
public bool Field1
{
get
{
return _field1;
}
set
{
if(value.GetType() == typeof(bool))
{
_field1 = value;
}
else
{
throw new ArgumentException("Value "+value+" is not of valid type."); // type of exception can be as per the failure observed
}
}
}
Now, within the action method, you should check for ModelState errors. For that you can do:
public IHttpActionResult SomeAction([FromBody] RequestModel request)
{
if(!ModelState.IsValid)
{
foreach (var modelState in ModelState.Values)
{
foreach (var error in modelState.Errors)
{
//collate the errors and send appropriate response back
}
}
}
else
{
// do your usual logic
}
}
You will be getting the exception in a format like:
Exception = {System.ArgumentException: Value 1 is not of
valid type .......
Hope this helps.
Related
I have an enum defined as:
public static enum State {
#JsonProperty("At Rest")
AT_REST,
#JsonProperty("In Motion")
IN_MOTION,
#JsonProperty("Stalled")
STALLED;
}
So, the server produces "At Rest" when Jackson serializes the AT_REST enum into JSON. Similarly, Jackson deserializes "At Rest" into AT_REST when the client passes JSON to the server. For example:
#GetMapping()
public State[] getAllStates() {
return State.values(); //returns JSON ["At Rest", "In Motion", "Stalled"]
}
#PostMapping()
public void saveState(#ResponseBody State state /*when client sends "At Rest", it gets converted into Enum*/) {
//save state
}
I also have a search GET endpoint. The client calls it with a "state" query parameter such https://localhost/search?state=At Rest. Since the query parameter value is not JSON, I have a Spring converter:
#Component
public class StringToStateConverter implements Converter<String, State> {
#Override
public State convert(String description) {
if ("At Rest".equals(description)) {
return State.AT_REST;
} else if ("In Motion".equals(description)) {
return State.IN_MOTION;
} else if ("Stalled".equals(description)) {
return State.STALLED;
} else {
return null;
}
}
}
Is it possible to have Spring use JsonProperty when deserializing a query param? If not, how can I avoid having the String description in multiple places in my code? I prefer not to make a description field in the enum since this is really just for client display.
Is it possible to have Spring use JsonProperty when deserializing a query param?
Yes.
#Component
#RequiredArgsConstructor
public class StringToStateConverter implements Converter<String, State> {
private final ObjectMapper mapper;
#Override
public State convert(String description) {
try {
return mapper.readValue("\"" + description + "\"", State.class);
} catch (JsonProcessingException e) {
// code to return error to client
}
}
I want to test a contract where one field is of type java.time.Instant. But not all instances of an Instant are handled as I expect by spring-cloud-contract. Given the following simple contract:
Contract.make {
description("Get a version")
request {
method 'GET'
url '/config/version'
headers {
contentType(applicationJson())
}
}
response {
status 200
body(
nr: 42,
creationDate: producer(anyIso8601WithOffset())
)
headers {
contentType(applicationJson())
}
}
}
And this service implementation:
#RestController
public class VersionController {
#GetMapping(path = "/version")
public ResponseEntity<Version> getCurrentVersion() {
return ResponseEntity.ok(new Version(42, Instant.ofEpochMilli(0)));
}
}
Executing gradle test works fine. But if I replace the Instant with Instant.now(), my provider test fails with
java.lang.IllegalStateException: Parsed JSON [{"nr":42,"creationDate":"2018-11-11T15:28:26.958284Z"}] doesn't match the JSON path [$[?(#.['creationDate'] =~ /([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.\d{3})?(Z|[+-][01]\d:[0-5]\d)/)]]
which is understandable because Instant.now() produces an Instant whose string representation does indeed not match the anyIso8601WithOffset() pattern. But why is this? Why are Instants represented differently and how can I describe a contract that validates for any instant?
Ok, I found a solution that works for me. Although I do not know if this is the way to go.
In order to always get the exact same format of the serialized instant, I define the format of the corresponding property of my version bean as follows:
public class Version {
private final int nr;
private final Instant creationDate;
#JsonCreator
public Version(
#JsonProperty("nr") int nr,
#JsonProperty("creationDate") Instant creationDate)
{
this.nr = nr;
this.creationDate = creationDate;
}
public int getNr() {
return nr;
}
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX", timezone = "UTC")
public Instant getCreationDate() {
return creationDate;
}
}
I am using Spring version 4.2 with Spring boot. I have a post request
http://localhost:3000/api/standards/
for which I have the following json request body
{
"standardName": "MyStandard",
"language": "Java",
}
All I want is to save a Standard entity. The 'language' property of the StandardEntity is of type Enum.
My Controller method looks like this
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Standard> createStandard(#Validated #RequestBody Standard standard ) {
standardService.createStandard(standard);
return new ResponseEntity<Standard>(standard, HttpStatus.CREATED);
}
But inside the controller the standard.getLangauge() is always null.
I have tried the #InitBinder and adding #JsonCreator to the Language enum but none of them works for me.
My Enum looks like this
public enum Language {
#JsonProperty("java")Java("java"),
#JsonProperty("sql")Sql("sql");
private String value;
private Language(String value) {
this.value = value;
}
public static Language fromValue(String value) {
for (Language language : values()) {
if (language.value.equalsIgnoreCase(value)) {
return language;
}
}
throw new IllegalArgumentException(
"Unknown enum type " + value + ", Allowed values are " + Arrays.toString(values()));
}
#Override
public String toString() {
return value;
}
#JsonCreator
public static Language create(String value) {
System.out.println("in json creator "+value);
if (value == null) {
throw new IllegalArgumentException();
}
for (Language v : values()) {
if (value.equals(v.getShortName())) {
return v;
}
}
throw new IllegalArgumentException();
}
Your request should be "java" instead of "Java"
{
"standardName": "MyStandard",
"language": "java"
}
When you are sending "Java" it in not able to map it to any Enum, so ideally it should throw an exception of type HttpMessageNotReadableException, check the stack trace there should be such exception.
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 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.