I created a database with JSON columns in mysql.
API defined in Swagger.
Writing JSON to the database works without problems, but when reading, the value of the JSON field is shown as an escaped string and not JSON
Here is part of model:
/// <summary>
/// Gets or Sets Doc
/// </summary>
[DataMember(Name="doc")]
public Dictionary<string, string> Doc { get; set; }
I also tried with string type and Dictionary<string, object> but unsuccessful.
Get method is here:
public virtual IActionResult GetDataById([FromRoute][Required]int? dataId, [FromRoute][Required]string jsonNode)
{
if(_context.Data.Any( a => a.Id == dataId)) {
var dataSingle = _context.Data.SingleOrDefault( data => data.Id == dataId);
return StatusCode(200, dataSingle);
} else {
return StatusCode(404);
}
}
}
And resulting JSON resposne looks like this one:
{
"field1": "value1",
"field2": "value2",
"doc": "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":{\"subkey3-1\":\"value3-1\",\"subkey3-2\":\"value3-2\"}}"
}
but correct JOSN should be linke this one:
{
"field1": "value1",
"field2": "value2",
"doc": {
"key1":"value1",
"key2":"value2",
"key3":{
"subkey3-1":"value3-1",
"subkey3-2":"value3-2"
}
}
}
If I try to return just "Doc" (JSON) field, response JSON is properly formatted.
I tried different serialization/deserialization but unsuccessful.
If I have public Dictionary<string, string> Doc { get; set; } in Model I got error:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HM1VN0F0I0IM", Request id "0HM1VN0F0I0IM:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: The property 'Data.Doc' is of type 'Dictionary<string, string>' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'
and for public string Doc { get; set; } in Model I got "doc" field value as escaped string as I mention above.
What would be the best way to go about this?
A few days later I found a resolution.
You need a one helper method that converts a string to object, exactly JObject if you use Json.NET - Newtonsoft.
public static JObject getJsonOutOfData (dynamic selectedData)
{
var data = JsonConvert.SerializeObject(selectedData);
var jsonData = (JObject)JsonConvert.DeserializeObject<JObject>(data);
if (selectedData.Doc != null) {
JObject doc = JObject.Parse(selectedData.Doc);
JObject docNode = JObject.Parse("{\"doc\":" + doc.ToString() + "}");
var jsonDoc = (JObject)JsonConvert.DeserializeObject<JObject>(docNode.ToString());
jsonData.Property("doc").Remove();
jsonData.Merge(jsonDoc, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
});
}
return jsonData;
}
and call it in the IActionResult method
public virtual IActionResult GetDataById([FromRoute][Required]int? accidentId, [FromRoute][Required]string jsonNode)
{
if (_context.Data.Any( a => a.Id == accidentId)) {
var accidentSingle = _context.Data.SingleOrDefault( accident => accident.Id == accidentId);
var result = getJsonOutOfData(accidentSingle);
return StatusCode(200, result.ToString());
} else {
return StatusCode(404);
}
}
In order to use unescaped JSON in POST/PUT requests, you should have a string type as a property type of JSON filed in the database model, and create an additional "request" model and set JSON property as "Object" type:
Database Model:
[DataMember(Name="doc")]
public string Doc { get; set; }
Request Model:
[DataMember(Name="doc")]
public Object Doc { get; set; }
For example Create model looks like:
public virtual IActionResult CreateData([FromBody]DataCreateRequest body)
{
var accidentObject = new Data() {
ColumnOne = body.ColumnOne,
ColumnTwo = body.ColumnTwo,
ColumnThree = body.ColumnThree,
Doc = (bodyDoc != null) ? body.Doc.ToString() : "{}"
};
_context.Data.Add(accidentObject);
_context.SaveChanges();
return StatusCode(200, getJsonOutOfData(accidentObject));
}
Related
The work of transforming JSON data into a typed data model through seems to be made much more complex by the "help" the combination of SharePoint and MS Graph offer. :-)
I have a SharePoint List in Microsoft 365 that I'm accessing through the Graph API in C#, where the query destination is a typed class with properties identical to the SharePoint List Column Properties.
The ListItem class Graph API returns the results in the a Fields.AdditionalData of type Dictionary<string,object{System.Text.Json.JsonElement}> It needs to become an IEnumerable<DataItem>, which I can do by taking the List from the query result through a Serialize/Deserialize round trip, as below:
var backToJSON = ListItems.Select(o => System.Text.Json.JsonSerializer.Serialize(o.Fields.AdditionalData));
var stronglyTypedItems = backToJSON.Select(jsonO => System.Text.Json.JsonSerializer.Deserialize<DataItem>(jsonO));
Is there a way to do this, either with smarter OData or something in Graph API I haven't seen, without taking what used to be JSON and sending it back through JSON Serializers twice?
More details below:
Sample output JSON from Graph Explorer, where value contains an array of :
"value" : [
{ "id": "1001,
"fields": {
"Column" : "true",
"Column2" : "value2",
"Column3" : "65"
}
},
{ "id": "1002,
"fields": {
<and so forth until the array terminates>
]
}
Corresponding C# Class (literally built using "Paste JSON as class"):
Public class DataItem {
public bool Column {get; set;}
public string Column2 {get; set;}
public int Column3 {get; set;}
}
The "Helper" classes in the C# Graph API deliver mostly transformed into the array of fields I actually need:
private static GraphServiceClient graphClient;
public static IListItemsCollectionRequest LicenseExpirationsList => graphClient
.Sites["<guid>"]
.Lists["<nameOfList>"].Items
.Request()
.Header("Accept", "application/json;odata.metadata=none")
.Select("fields,id")
.Expand("fields");
var ListItems = (await GraphHelper.LicenseExpirationsList.GetAsync()).CurrentPage;
// JSON round tripping through JSONSerializer to get the strong type...
// But why? ListItems.Fields.AdditionalData is a Dictionary of JSON elements in the first place!
var backToJSON = ListItems.Select(o => System.Text.Json.JsonSerializer.Serialize(o.Fields.AdditionalData));
var stronglyTypedItems = backToJSON.Select(jsonO => System.Text.Json.JsonSerializer.Deserialize<DataItem>(jsonO));
return stronglyTypedItems;
You could customize the client's JSON serialization to return a derived type of default FieldValueSet.
First, define your own extended FieldValueSet:
public class FieldValueSetWithDataItem : FieldValueSet
{
public bool Column { get; set; }
public string Column2 { get; set; }
public int Column3 { get; set; }
}
Second, implement your own JSON converter:
class CustomFieldValueSetJsonConverter : JsonConverter<FieldValueSet>
{
private static readonly JsonEncodedText ODataTypeProperty
= JsonEncodedText.Encode("#odata.type");
private static readonly JsonEncodedText IdProperty
= JsonEncodedText.Encode("id");
private static readonly JsonEncodedText ColumnProperty
= JsonEncodedText.Encode("Column");
private static readonly JsonEncodedText Column2Property
= JsonEncodedText.Encode("Column2");
private static readonly JsonEncodedText Column3Property
= JsonEncodedText.Encode("Column3");
public override FieldValueSet Read(ref Utf8JsonReader reader,
Type typeToConvert, JsonSerializerOptions options)
{
var result = new FieldValueSetWithDataItem();
using var doc = JsonDocument.ParseValue(ref reader);
var root = doc.RootElement;
foreach (var element in root.EnumerateObject())
{
if (element.NameEquals(ODataTypeProperty.EncodedUtf8Bytes))
{
result.ODataType = element.Value.GetString();
}
else if (element.NameEquals(IdProperty.EncodedUtf8Bytes))
{
result.Id = element.Value.GetString();
}
else if (element.NameEquals(ColumnProperty.EncodedUtf8Bytes))
{
result.Column = element.Value.GetBoolean();
}
else if (element.NameEquals(Column2Property.EncodedUtf8Bytes))
{
result.Column2 = element.Value.GetString();
}
else if (element.NameEquals(Column3Property.EncodedUtf8Bytes))
{
result.Column3 = element.Value.GetInt32();
}
else
{
// Capture unknown property in AdditionalData
if (result.AdditionalData is null)
{
result.AdditionalData = new Dictionary<string, object>();
}
result.AdditionalData.Add(element.Name, element.Value.Clone());
}
}
return result;
}
public override void Write(Utf8JsonWriter writer,
FieldValueSet value, JsonSerializerOptions options)
{
// To support roundtrip serialization:
writer.WriteStartObject();
writer.WriteString(ODataTypeProperty, value.ODataType);
writer.WriteString(IdProperty, value.Id);
if (value is FieldValueSetWithDataItem dataItem)
{
writer.WriteBoolean(ColumnProperty, dataItem.Column);
writer.WriteString(Column2Property, dataItem.Column2);
writer.WriteNumber(Column3Property, dataItem.Column3);
}
if (value.AdditionalData is not null)
{
foreach (var kvp in value.AdditionalData)
{
writer.WritePropertyName(kvp.Key);
((JsonElement)kvp.Value).WriteTo(writer);
}
}
writer.WriteEndObject();
}
}
Last, use the JSON converter when making your request:
// Use custom JSON converter when deserializing response
var serializerOptions = new JsonSerializerOptions();
serializerOptions.Converters.Add(new CustomFieldValueSetJsonConverter());
var responseSerializer = new Serializer(serializerOptions);
var responseHandler = new ResponseHandler(responseSerializer);
var request = (ListItemsCollectionRequest)client.Sites[""].Lists[""].Items.Request();
var listItems = await request
.WithResponseHandler(responseHandler)
.GetAsync();
To access your column values:
var col3 = ((FieldValueSetWithDataItem)listItem.Fields).Column3;
You may find the HttpProvider of the GraphServiceClient helpful in this scenario:
var listItemsCollectionRequest = graphServiceClient
.Sites["<guid>"]
.Lists["<nameOfList>"]
.Items
.Request()
.Header("Accept", "application/json;odata.metadata=none")
.Select("fields,id")
.Expand("fields");
using (var requestMessage = listItemsCollectionRequest.GetHttpRequestMessage())
{
using var responseMessage = await graphServiceClient.HttpProvider.SendAsync(requestMessage);
//deserialize the response body into DataItem
}
By using the HttpProvider you can directly work with the response from the Graph API and deserialize the response body into your custom class.
Can somebody help me to parse the json and get the details.
Lets say i have Top2 input parameter as Police and i want to know the respective Top3 and in that Top3 array i need to check whether Test1Child value is there or not.
I am using newtonsoft json + c# and so far i can get till DeviceTypes using below code
var json = File.ReadAllText(jsonFile); // below json is stored in file jsonFile
var jObject = JObject.Parse(json);
JArray MappingArray =(JArray)jObject["Top1"];
string strTop1 = ObjZone.Top1.ToString();
string strTop2 = ObjZone.Top2.ToString();
var JToken = MappingArray.Where(obj => obj["Top2"].Value<string>() == strTop2).ToList();
//Json
{
"Top1": [
{
"Top2": "Test1",
"Top3": [
"Test1Child"
]
},
{
"Top2": "Test2",
"Top3": [
"Test2Child"
]
}
]
}
I'd use http://json2csharp.com/ (or any other json to c# parser) and then use C# objects (it's just easier for me)
This would look like that for this case:
namespace jsonTests
{
public class DeviceTypeWithResponseTypeMapper
{
public string DeviceType { get; set; }
public List<string> ResponseTypes { get; set; }
}
public class RootObject
{
public List<DeviceTypeWithResponseTypeMapper> DeviceTypeWithResponseTypeMapper { get; set; }
}
}
and the use it like that in code:
var rootob = Newtonsoft.Json.JsonConvert.DeserializeObject<RootObject>(str);
var thoseThatHaveNotUsed = rootob.DeviceTypeWithResponseTypeMapper.Where(dtwrtm =>
dtwrtm.ResponseTypes.Any(rt => rt == "NotUsed"));
foreach (var one in thoseThatHaveNotUsed)
{
Console.WriteLine(one.DeviceType);
}
this code lists all the Device types that have "NotUsed" among the responses.
version 2 (extending your code) would look like that, i believe:
static void Main(string[] args)
{
var json = str; // below json is stored in file jsonFile
var jObject = JObject.Parse(json);
JArray ZoneMappingArray = (JArray)jObject["DeviceTypeWithResponseTypeMapper"];
string strDeviceType = "Police";
string strResponseType = "NotUsed";
var JToken = ZoneMappingArray.Where(obj => obj["DeviceType"].Value<string>() == strDeviceType).ToList();
var isrespTypeThere = JToken[0].Last().Values().Any(x => x.Value<string>() == strResponseType);
Console.WriteLine($"Does {strDeviceType} have response type with value {strResponseType}? {yesorno(isrespTypeThere)}");
}
private static object yesorno(bool isrespTypeThere)
{
if (isrespTypeThere)
{
return "yes!";
}
else
{
return "no :(";
}
}
result:
and if you'd like to list all devices that have response type equal to wanted you can use this code:
var allWithResponseType = ZoneMappingArray.Where(jt => jt.Last().Values().Any(x => x.Value<string>() == strResponseType));
foreach (var item in allWithResponseType)
{
Console.WriteLine(item["DeviceType"].Value<string>());
}
So I have this Web API that calls a service which in turn, returns a json string.
The string looks a bit like this:
{
"title": "Test",
"slug": "test",
"collection":{ },
"catalog_only":{ },
"configurator":null
}
I have cut this down considerably to make it easier to see my issue.
When I make my API call, I then use a Factory method to factorize the response which looks something like this:
/// <summary>
/// Used to create a product response from a JToken
/// </summary>
/// <param name="model">The JToken representing the product</param>
/// <returns>A ProductResponseViewModel</returns>
public ProductResponseViewModel Create(JToken model)
{
// Create our response
var response = new ProductResponseViewModel()
{
Title = model["title"].ToString(),
Slug = model["slug"].ToString()
};
// Get our configurator property
var configurator = model["configurator"];
// If the configurator is null
if (configurator == null)
throw new ArgumentNullException("Configurator");
// For each item in our configurator data
foreach (var item in (JObject)configurator["data"])
{
// Get our current option
var option = item.Value["option"].ToString().ToLower();
// Assign our configuration values
if (!response.IsConfigurable) response.IsConfigurable = (option == "configurable");
if (!response.IsDesignable) response.IsDesignable = (option == "designable");
if (!response.HasGraphics) response.HasGraphics = (option == "graphics");
if (!response.HasOptions) response.HasOptions = (option == "options");
if (!response.HasFonts) response.HasFonts = (option == "fonts");
}
// Return our Product response
return response;
}
}
Now, as you can see I am getting my configurator property and then checking it to see if it's null.
The json string shows the configurator as null, but when I put a breakpoint on the check, it actually shows it's value as {}.
My question is how can I get it to show the value (null) as opposed to this bracket response?
You're asking for the JToken associated with the configurator key. There is such a token - it's a null token.
You can check this with:
if (configurator.Type == JTokenType.Null)
So if you want to throw if either there's an explicit null token or configurator hasn't been specified at all, you could use:
if (configurator == null || configurator.Type == JTokenType.Null)
For those who use System.Text.Json in newer .Net versions (e.g. .Net 5):
var jsonDocument = JsonDocument.Parse("{ \"configurator\": null }");
var jsonElement = jsonDocument.RootElement.GetProperty("configurator");
if (jsonElement.ValueKind == JsonValueKind.Null)
{
Console.WriteLine("Property is null.");
}
public class Collection
{
}
public class CatalogOnly
{
}
public class RootObject
{
public string title { get; set; }
public string slug { get; set; }
public Collection collection { get; set; }
public CatalogOnly catalog_only { get; set; }
public object configurator { get; set; }
}
var k = JsonConvert.SerializeObject(new RootObject
{
catalog_only =new CatalogOnly(),
collection = new Collection(),
configurator =null,
slug="Test",
title="Test"
});
var t = JsonConvert.DeserializeObject<RootObject>(k).configurator;
Here configurator is null.
I have a json response displayed as below. I am using datacontractserializer to serialize.
If I need only "text" and "created time" from this Json response...how should be my DataContract looks like?
Do I need to have all these six properties in my data contract ? and use "IgnoreDataMember" as attribute?
Also, do I need to give same name for my properties in datacontract (Ex : screenName, text as property name ?)
"abcDetails":[
{
"screenName":"my name",
"text":"tweet desc",
"createdTime":1423494304000,
"entities":{ },
"name":"abc",
"id":"123"
}]
To answer your questions:
You can omit properties that you do not need and DataContractJsonSerializer will skip over them.
You class's property names can differ from the JSON property names as long as you set the DataMemberAttribute.Name value to be the same as the name appearing in the JSON.
Your JSON is invalid, it is missing outer braces. I assume that's just a copy/paste error in your question.
Thus your classes could look like:
[DataContract]
public class Detail
{
[DataMember(Name="text")]
public string Text { get; set; }
[DataMember(Name="createdTime")]
public long CreatedTimeStamp { get; set; }
}
[DataContract]
public class RootObject
{
[DataMember(Name="abcDetails")]
public List<Detail> Details { get; set; }
}
And to test:
string json = #"
{
""abcDetails"":[
{
""screenName"":""my name"",
""text"":""tweet desc"",
""createdTime"":1423494304000,
""entities"":{ },
""name"":""abc"",
""id"":""123""
}]
}
";
var response = DataContractJsonSerializerHelper.GetObject<RootObject>(json);
foreach (var detail in response.Details)
{
Console.WriteLine(string.Format("Created Time: {0}; Text: \"{1}\"", detail.CreatedTimeStamp, detail.Text));
}
produces the output
Created Time: 1423494304000; Text: "tweet desc"
using the helper class:
public static class DataContractJsonSerializerHelper
{
public static T GetObject<T>(string json) where T : class
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
return GetObject<T>(json, serializer);
}
public static T GetObject<T>(string json, DataContractJsonSerializer serializer)
{
using (var stream = GenerateStreamFromString(json))
{
var obj = serializer.ReadObject(stream);
return (T)obj;
}
}
private static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
}
}
I have a C# Website and Web API combined with a Save-method in one of my API Controllers. This method only allows HttpPosts. What I want to send in the body is a List<int>, List<decimal> and long.
Since HttpPost methods in API Controllers only allow one parameter to work, I tried both a JObject and string as parameter. When I use string it's always null, when I use JObject it's not null but incorrectly copied from the body. Because these are null and incorrectly copied, the converting to the List<int>, List<decimal> and long are also not working.
Parameter as string:
[HttpPost]
[AllowAnonymous]
[ActionName("save")]
public bool SaveOrders([FromBody] string jsonData)
{
// Convert jsonData string to List<int>, List<decimal>, long
JObject json = JObject.Parse(jsonData);
List<int> productIds = JsonConvert.DeserializeObject(json["productIds"].ToString(), typeof(List<int>));
List<decimal> prices = JsonConvert.DeserializeObject(json["prices"].ToString(), typeof(List<decimal>));
long dateInTicks = JsonConvert.DeserializeObject(json["dateInTicks"].ToString(), typeof(long));
...
}
with POST-body:
"{
"productIds": "[20, 25]",
"prices": "[0.40, 7.40]",
"dateInTicks": "1402444800"
}"
When I debug this above, the parameter-string is always null.
Parameter as JObject:
[HttpPost]
[AllowAnonymous]
[ActionName("save")]
public bool SaveOrders([FromBody] JObject jsonData)
{
// Convert jsonData JObject to List<int>, List<decimal>, long
dynamic json = jsonData;
string sProductIds = (string)json["productIds"];
string sPrices = (string)json["prices"];
string sDateInTicks = (string)json["dateInTicks"];
List<int> productIds = JsonConvert.DeserializeObject(sProductIds, typeof(List<int>));
List<decimal> prices = JsonConvert.DeserializeObject(sPrices, typeof(List<decimal>));
long dateInTicks = JsonConvert.DeserializeObject(sDateInTicks, typeof(long));
...
}
with POST-body:
productIds: "[20, 25]",
prices: "[0.40, 7.40]",
dateInTicks: "1402444800"
When I debug this, the parameter-JObject is:
{
"productIds: \"": {
"20, 25]\",\nprices: \"": {
"0.40, 7.40]\",\ndateInTicks: \"1402444800\"": ""
}
}
}
and the sProductIds, sPrices and sDateInTicks are null.
I know I'm doing some things wrong, therefore this question, since I don't how I should change it.
Edit 1 (Rafal's suggestion):
In my config file I added one line:
// Only allow JSON response format
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
// Added the following line:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
And my method is changed:
[HttpPost]
[AllowAnonymous]
[ActionName("save")]
public bool SaveOrders([FromBody] Data jsonData)
{
if (jsonData != null)
{
if (jsonData.productIds != null && jsonData.prices != null)
{
return SavePrices(jsonData.productIds, jsonData.prices, jsonData.dateInTicks);
}
else
{
Console.WriteLine("One of the objects is null, so we can't continue.");
return false;
}
}
else
{
Console.WriteLine("The send data is null, so we can't continue.");
return false;
}
}
public class Data
{
public List<int> productIds { get; set; }
public List<decimal> prices { get; set; }
public long dateInTicks { get; set; }
}
But although the Data-parameter isn't null, both the Lists inside of it are and the long is 0 as well.
Edit 2:
With the FireFox' RESTClient Body:
"productIds":[20,25],"prices":[0.4,7.4],"dateInTicks":1402444800
Why is the JObject parameter this:
{
"\"productIds\":": {
"20,25],\"prices\":": {
"0.4,7.4],\"dateInTicks\":1402444800": ""
}
}
}
instead of this:
{
\"productIds\":[20,25],\"prices\":[0.4,7.4],\"dateInTicks\":1402444800
}
It automatically removes the first [ with the arrays and replaces it with :" {"..
Add a class:
public class Data
{
public List<int> productIds { get; set; }
public List<decimal> prices { get; set; }
public long dateToTicks { get; set; }
}
And change the signature of you API method to receive it as parameter.
Note that properties names are pascal case. I did that on purpose as default configuration of deserializer will not allow camel case. To handle your json and use 'normal' properties casing add:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver
= new CamelCasePropertyNamesContractResolver();
line in your site configuration file you will find it in App_Start folder for newer template or directly in Global.asax.cs.
The config is of type HttpConfiguration.
Further investigation leads me to conclusion that you have invalid (well not invalid as far as json validator is concerned but not appropriate to the task at hand) json content. If you post it as:
{
"productIds": [20, 25],
"prices": [0.40, 7.40],
"dateToTicks": "1402444800"
}
without any additional quotes then it will deserialize to Data class. If you need to retain it as is then you would need to deserialize is by hand as if you loose wraping quote then you have json object with 3 string properties not lists of values.