JSON model binding with XML Serialisation attributes in Web API - json

I have an API controller with the following action signature...
[HttpPost]
public HttpResponseMessage DoSearch(SearchParameters parameters)
SearchParameters is not something i can modify, and the decompiled source looks like this...
[DebuggerStepThrough]
[XmlRoot("SearchData", IsNullable = false, Namespace = "http://company.com/some/namespace/v1")]
[GeneratedCode("xsd", "2.0.50727.3038")]
[DesignerCategory("code")]
[XmlType(Namespace = "http://company.com/some/namespace/v1")]
[Serializable]
public class SearchParameters
{
private string[] _searchCodes;
[XmlArrayItem("SearchCode", IsNullable = false)]
public string[] SearchCodes
{
get
{
return this._searchCodes;
}
set
{
this._searchCodes = value;
}
}
}
I can call the endpoint successfully with an XML payload, but cannot get JSON to work at all. The SearchCodes property is always null.
If i replace the SearchParameters Type with a POCO that has none of the Xml Serialisation attributes on it, it works fine with JSON.
This led me to think that the JsonMediaTypeFormatter is unable to match up the property correctly due to the xml serialisation attributes (even though this shouldnt matter, as its JSON, not XML right?).
I changed the JsonFormatter to use the DataContract Serializer, but that has made no difference.
httpConfiguration.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;
I tried crafting different structures of JSON to see if i could 'help' it understand, but none of these work either...
{
"SearchData": {
"SearchCodes": {
"SearchCode": [
"SYAA113F",
"N0TEXI5T",
"SYAA112C"
]
}
}
}
{
"SearchCodes": {
"SearchCode": [
"SYAA113F",
"N0TEXI5T",
"SYAA112C"
]
}
}
{
"SearchCodes": [
"SYAA113F",
"N0TEXI5T",
"SYAA112C"
]
}
{
"SearchData": {
"SearchCode": [
"SYAA113F",
"N0TEXI5T",
"SYAA112C"
]
}
}
{
"SearchCode": [
"SYAA113F",
"N0TEXI5T",
"SYAA112C"
]
}
{
"SearchCodes": [
{ "SearchCode" : "SYAA113F" },
{ "SearchCode" : "SYAA113F" },
{ "SearchCode" : "SYAA113F" }
]
}
How can i debug this further? and what am i missing?
What is causing the JSON media formatter to behave differently due to the XML attributes?

Post this JSON and see.
{"_searchCodes":[
"SYAA113F",
"N0TEXI5T",
"SYAA112C"]
}
Remember to set Content-Type: application/json.

Related

Newtonsoft.json SelectToken Replace differs from SelectTokens Replace in foreach with a NullReferenceException

Hope anybody could guide me here. I spend some hours on it and can't understand what's going on.
Mission: Replace a json element by a jsonpath search tag. (sort of $ref feature)
In my code example below i want to replace the value of DataReaderUser by a value found by the json path search $.UsersAndGroups.Users[?(#.Name == 'OMDASAccountUser')].Username . In this case it should result in the value "contoso\SVCSCOM-DO-OMDAS"
The code below works as expected.. the issue is below this code ..
https://dotnetfiddle.net/gEjggK
using System;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
string json = #"{
""SQLServer"": {
""SQLReportingServices"": {
""AccountSettings"": {
""DataReaderUser"": {""$JsonPath"": ""$.UsersAndGroups.Users[?(#.Name == 'OMDASAccountUser')].Username""},
}
}
},
""UsersAndGroups"": {
""Users"": [
{
""Name"": ""OMActionAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
},
{
""Name"": ""OMDASAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
}
]
}
}";
JObject jo = JObject.Parse(json);
var JsonPath = jo.SelectToken("..$JsonPath");
JsonPath.Parent.Parent.Replace(jo.SelectToken(JsonPath.ToString()));
Console.WriteLine(jo.ToString());
}
}
The output will be :
{
"SQLServer": {
"SQLReportingServices": {
"AccountSettings": {
"DataReaderUser": "contoso\\SVCSCOM-DO-OMDAS"
}
}
},
"UsersAndGroups": {
"Users": [
{
"Name": "OMActionAccountUser",
"Username": "contoso\\SVCSCOM-DO-OMDAS"
},
{
"Name": "OMDASAccountUser",
"Username": "contoso\\SVCSCOM-DO-OMDAS"
}
]
}
}
Now the issue:
I want to do the same for all possible jsonpaths refers. So i use the SelectTokens and an foreach . But it looks like the behavior is different , the parents are null.
https://dotnetfiddle.net/lZW3XP
using System;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
string json = #"{
""SQLServer"": {
""SQLReportingServices"": {
""AccountSettings"": {
""DataReaderUser"": {""$JsonPath"": ""$.UsersAndGroups.Users[?(#.Name == 'OMDASAccountUser')].Username""},
}
}
},
""UsersAndGroups"": {
""Users"": [
{
""Name"": ""OMActionAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
},
{
""Name"": ""OMDASAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
}
]
}
}";
JObject jo = JObject.Parse(json);
var JsonPaths = jo.SelectTokens("..$JsonPath");
foreach (var JsonPath in JsonPaths )
{
JsonPath.Parent.Parent.Replace(jo.SelectToken(JsonPath.ToString()));
}
Console.WriteLine(jo.ToString());
}
}
And the output:
Run-time exception (line 34): Object reference not set to an instance of an object.
Stack Trace:
[System.NullReferenceException: Object reference not set to an instance of an object.]
at Newtonsoft.Json.Linq.JsonPath.PathFilter.GetNextScanValue(JToken originalParent, JToken container, JToken value)
at Newtonsoft.Json.Linq.JsonPath.ScanFilter.<ExecuteFilter>d__4.MoveNext()
at Program.Main() :line 34
would be great to get some directions since i am spinning my head here.
michel
SelectTokens uses lazy evaluation and if you modify the token while enumerating all matches it can break in unexpected ways. A simple fix is to add ToArray() to force eager evaluation:
var JsonPaths = jo.SelectTokens("..$JsonPath").ToArray();

Net core dapper and postgres jsonb column

I want to POST some custom JSON to my postgres jsonb column via postman using the below request.
The custom part is sent in the "Settings" > "data" node. I don't want to apply the custom part to a model I just want to send in any kind of json and store it.
{
"name": "Test",
"settings": {
"data": {
"customdata": "hello",
"custommore": "bye"
}
}
}
The "data" node is modelled - like this:
public string Data { get; set; } //I have tried JSONDocument and Jsonb types to no avail.
Postman errors with this:
"errors": {
"$.settings.data": [
"The JSON value could not be converted to System.String. Path: $.settings.data | LineNumber: 3 | BytePositionInLine: 17."
]
}
The request doesn't even hit my controller method. I think it is because the customdata and custommore is not mapped to a model.
Is there a way of sending in custom JSON data that is not fixed to a model of any kind - or must it be part of a model?
I'm struggling to find anything about this that doesn't relate to EF core which is not what I am using.
You can use custom model binding,and get json data from HttpContext.Request.Body,and then use sonConvert.DeserializeObject to get json object.You can set the data to the format you want.
Here is a demo:
DataBinder:
public class DataBinder:IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model1 = new Customer();
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var body = reader.ReadToEndAsync();
var mydata = JsonConvert.DeserializeObject<JObject>(body.Result);
model1.Name = mydata["name"].ToString();
model1.Settings = new Settings
{
Data = mydata["settings"]["data"].ToString()
};
}
bindingContext.Result = ModelBindingResult.Success(model1);
return Task.CompletedTask;
}
}
Controller:
public IActionResult TestCustomModelBinding([ModelBinder(BinderType = typeof(DataBinder))]Customer customer) {
return Ok();
}
result:

Jackson serialize object as key of map

Hi I'm facing problem with serialization of map where key is an custom class.
data class KeyClass(val id: Int, val name: String) {
fun toJSON() = "\"KeyClass\": {\"id\":$id,\"name\":\"$name\"}"
}
Invocation:
fun method(): Map<KeyClass, List<Something>> = ...
My jackson Serializer ofc I'm also adding this as module in objectMapper:
class KeyClassSerializer : JsonSerializer<KeyClass>() {
override fun serialize(value: KeyClass, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeRawValue(value.toJSON())
}
}
class KeyClassSerializerModule : SimpleModule() {
init {
addKeySerializer(KeyClass::class.java, KeyClassSerializer())
}
}
And JSON I'm receiving is:
"\"KeyClass\": {\"id\":1,\"name\":\"Thomas\"}" : [Something:...]
I mean the value of map is serialized correctly but key isn't.
I assume the expected result is :
"KeyClass": {
"id": 1,
"name":"Thomas"
} : [...]
But it's not valid Json. You can still do something like :
{
"key" : {
"id": 1,
"name":"Thomas"
},
"value" : [...]
}

Testing WebAPI Controller with Multiple Put/Post parameters

So according to various suggestions I updated my web api controller method that accepts multiple complex parameter objects from
//public IHttpActionResult PostCreateCase([FromBody] ARC.Donor.Business.Case.CreateCaseInput CreateCaseInput, [FromBody] ARC.Donor.Business.Case.SaveCaseSearchInput SaveCaseSearchInput)
to
public IHttpActionResult PostCreateCase(JObject jsonObject)
{
}
and then parse them accordingly ...
public IHttpActionResult PostCreateCase(JObject jsonObject)
{
var CreateCaseInput = jsonObject["CreateCaseInput"].ToObject<CreateCaseInput>();
var SaveCaseSearchInput = jsonObject["SaveCaseSearchInput"].ToObject<SaveCaseSearchInput>();
ARC.Donor.Service.Case.CaseServices cs = new ARC.Donor.Service.Case.CaseServices();
var searchResults = cs.createCase(CreateCaseInput, SaveCaseSearchInput);
------
}
but even after that when I test my controller using the Json object as
"CreateCaseInput":[
{
"case_nm":"EFG Test",
"case_desc":"EFG is a test",
"report_dt" : "04/12/2015"
}
],
"SaveCaseSearchInput":[
{
"firstName" : "Chiranjib",
"constType" : "IN"
}
]
I still get CreateCaseInput and SaveCaseSearchInput objects in
public IList<ARC.Donor.Business.Case.CreateCaseOutput> createCase(ARC.Donor.Business.Case.CreateCaseInput CreateCaseInput, ARC.Donor.Business.Case.SaveCaseSearchInput SaveCaseSearchInput)
{
.......
}
to be null.
What am I doing wrong ?
You JSON is wrong because is an array, not a object.
Try this way:
{
"CreateCaseInput":
{
"case_nm":"EFG Test",
"case_desc":"EFG is a test",
"report_dt" : "04/12/2015"
},
"SaveCaseSearchInput":
{
"firstName" : "Chiranjib",
"constType" : "IN"
}
}
Hope it helps :)

CodeFluent JSON Serialization Does Not Work for All Fields

I'm using CodeFluent JsonUtilities to convert an object to JSON. Using anything else seems to have various other issues (e.g. Circular Referencing).
Here's some functions I use to convert to JSON for ASP.NET MVC, using CodeFluent.Runtime.Utilities namespace (for the JsonUtilities).
public static ContentResult ConvertToJsonResponse(object obj)
{
string json = JsonUtilities.Serialize(obj);
return PrepareJson(json);
}
/// <summary>
/// Converts JSON string to a ContentResult object suitable as a response back to the client
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public static ContentResult PrepareJson(string json)
{
ContentResult content = new ContentResult();
content.Content = json;
content.ContentType = "application/json";
return content;
}
The problem is when I use JsonUtilities to convert an object it seems to have skipped some nested objects.
For example, I tried to convert DataSourceResult object (from Telerik) to JSON using CodeFluent.
public ActionResult UpdateTeam([DataSourceRequest]DataSourceRequest request, TeamViewModel teamViewModel)
{
ModelState.AddModelError("", "An Error!");
DataSourceResult dataSourceResult = new[] { teamViewModel }.ToDataSourceResult(request, ModelState);
ContentResult ret = CodeFluentJson.ConvertToJsonResponse(dataSourceResult);
return ret;
}
The dataSourceResult holds three main properties:
Data - which hold my Model that contains my data.
Total - which holds the amount of data objects there are.
Errors - which holds all the errors of my MVC model. It is very nested, with a lot of properties under it.
When I try to use CodeFluent utilities to convert the DataSourceResult object it works to convert "Data", and "Total" fields, but with Errors, it skips it entirely, resulting in the below JSON string:
{
"Data":[
{
"ExampleProperty":"ExampleValue"
}
],
"Total":1,
"AggregateResults":null,
"Errors":{ }
}
I'm guessing the issue is with the "Errors" object being too nested for the CodeFluent converter.
So my question is are there any CodeFluent serialization options/code I'm missing to work with heavily nested objects for JSON conversion?
The problem comes from the fact you're creating a model error with an empty key. It's not forbidden but JsonUtilities just skip dictionary values with an empty key, by design.
Just use a real key, like this:
ModelState.AddModelError("my first error", "An Error!");
ModelState.AddModelError("my second error", "An Error!");
And you'll see the errors collection serialized, like this:
{
"Data":[
{
"ExampleProperty":"ExampleValue"
}],
"Total": 1,
"AggregateResults": null,
"Errors": {
"my first error": {
"errors": [
"An Error!"
]
},
"my second error": {
"errors": [
"An Error!"
]
}
}
}
Otherwise, if you really want to keep empty keys, then you can leverage the JsonUtilitiesOptions class that has callbacks for tweaking the serialization (and deserialization) process. Careful with this as you can easily break JSON syntax. This is how you could do it, in a way that should handle all cases:
JsonUtilitiesOptions options = new JsonUtilitiesOptions();
options.WriteValueCallback += (e) =>
{
IDictionary valueDic = e.Value as IDictionary;
if (valueDic != null)
{
e.Writer.Write('{');
bool first = true;
foreach (DictionaryEntry entry in valueDic)
{
if (!first)
{
e.Writer.Write(',');
}
else
{
first = false;
}
// reuse JsonUtilities already written functions
JsonUtilities.WriteString(e.Writer, entry.Key.ToString(), e.Options);
e.Writer.Write(':');
// object graph is for cyclic/infinite serialization checks
JsonUtilities.WriteValue(e.Writer, entry.Value, e.ObjectGraph, e.Options);
}
e.Writer.Write('}');
e.Handled = true; // ok, we did it
}
};
string json = JsonUtilities.Serialize(obj, options);
Now, you'll get this result:
{
"Data":[
{
"ExampleProperty":"ExampleValue"
}],
"Total": 1,
"AggregateResults": null,
"Errors": {
"": {
"errors": [
"An Error!"
]
}
}
}