Related
I am relatively new to .Net API development, so apologies if the answer is obvious here.
I have JSON coming in via a request. It is a nested JSON structure with a few subclasses.
I need to get this JSON in and subsequently restructure and distribute it into two, flat classes for storing in two database tables.
What is the best way to go about this? I have used DTOs and Automapper on my path to learning but I am not sure how to go about this with a nested structure.
The incoming JSON looks like this:
{
"id": xxx,
"parent_id": xxx,
"number": "xxx",
"order_key": "xxx",
"created_via": "xxx",
"version": "xxx",
"status": "xxx",
"currency": "xxx",
"date_created": "xxx",
"date_created_gmt": "xxx",
"date_modified": "xxx",
"date_modified_gmt": "xxx",
"discount_total": "xxx",
"discount_tax": "xxx",
"shipping_total": "xxx",
"shipping_tax": "xxx",
"cart_tax": "xxx",
"total": "xxx",
"total_tax": "xxx",
"prices_include_tax": xxx,
"customer_id": xxx,
"customer_ip_address": "xxx",
"customer_user_agent": "xxx",
"customer_note": "",
"billing": {
"first_name": "xxx",
"last_name": "xxx",
"company": "",
"address_1": "",
"address_2": "",
"city": "",
"state": "",
"postcode": "",
"country": "",
"email": "xxx",
"phone": "xxx"
},
"shipping": {
"first_name": "xxx",
"last_name": "xxx",
"company": "",
"address_1": "",
"address_2": "",
"city": "",
"state": "",
"postcode": "",
"country": ""
},
"payment_method": "xxx",
"payment_method_title": "xxx",
"transaction_id": "",
"date_paid": xxx,
"date_paid_gmt": xxx,
"date_completed": xxx,
"date_completed_gmt": xxx,
"cart_hash": "xxx",
"meta_data": [
{
"id": xxx,
"key": "xxx",
"value": "xxx"
}
],
"line_items": [
{
"id": xxx,
"name": "xxx",
"product_id": xxx,
"variation_id": xxx,
"quantity": xxx,
"tax_class": "",
"subtotal": "xxx",
"subtotal_tax": "xxx",
"total": "xxx",
"total_tax": "xxx",
"taxes": [],
"meta_data": [],
"sku": "",
"price": xxx
}
],
"tax_lines": [],
"shipping_lines": [],
"fee_lines": [],
"coupon_lines": [],
"refunds": [],
"_links": {
"self": [
{
"href": "xxx"
}
],
"collection": [
{
"href": "xxx"
}
],
"customer": [
{
"href": "xxx"
}
]
}
}
Only certain parts of it are relevant. It needs to be mapped into 2 classes as follows:
public class OnlineOrderHeader
{
[Key]
[Required]
public int OnlineOrderID { get; set; }
[Required]
public int AppOrderID { get; set; }
public int OrderNumber { get; set; }
[MaxLength(50)]
public string OrderKey { get; set; }
[MaxLength(50)]
public string CreatedVia { get; set; }
[MaxLength(50)]
public string Version { get; set; }
[MaxLength(50)]
public string Status { get; set; }
[MaxLength(3)]
public string Currency { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public decimal DiscountTotal { get; set; }
public decimal DiscountTax { get; set; }
public decimal CartTax { get; set; }
public decimal CartTotal { get; set; }
public int PriceIncludesTax { get; set; } //Check
public int CustomerID { get; set; }
[MaxLength(50)]
public string CustomerFirstName { get; set; }
[MaxLength(50)]
public string CustomerLastName { get; set; }
[MaxLength(50)]
public string CustomerEmail { get; set; }
[MaxLength(50)]
public string CustomerPhone { get; set; }
[MaxLength(50)]
public string CustomerEmployeeNo { get; set; }
[MaxLength(50)]
public string PaymentMethod { get; set; }
public int TransactionID { get; set; }
public DateTime DatePaid { get; set; }
[MaxLength(500)]
public string OrderURL { get; set; }
[MaxLength(500)]
public string CustomerNotes { get; set; }
[MaxLength(50)]
public string CuisineOrderStatus { get; set; }
}
And
public class OnlineOrderLines
{
[Key]
[Required]
public int OnlineOrderLineID { get; set; }
[Required]
public int AppOrderLineID { get; set; }
[Required]
public int ProductID { get; set; }
[MaxLength(50)]
public string SKU { get; set; }
public int Quantity { get; set; }
public decimal SubTotal { get; set; }
public decimal SubTotalTax { get; set; }
public decimal Money { get; set; }
[MaxLength(100)]
public string ProductDescription { get; set; }
[MaxLength(50)]
public string Category { get; set; }
}
I am not sure how to get one object with all necessary subclasses using a DTO.
Once I have that object, splitting it up into classes should be relatively stratforward, I would assume.
Any tips would be greatly appreciated!
Firstly, make sure your JSON is valid. The one above isn't. I think the data types need to be more obvious. You have "xxx" and xxx - the latter should be numbers I assume? anyway, I usually just use strings as primitives and worry about the numerical data types later.
Once you have a valid JSON (i.e. take the response string from the request and remove the escape characters - easily done with regex), you can paste it to an online JSON to C# converter (like this one).
This should give you a class structure. You can rename the RootObject to the main name of your API response class. I also check the primitive data types, as those converters are not perfect. As I mentioned above, I usually change the primitives to be strings and deal with data types later, when mapping to view models/entities.
Then, in your API calling class:
Make that API request (i.e httpClient.GetAsync(requestUrl))
Read a response as a string (i.e response.ReadAsStringAsync()
Use Newtonsoft.JSON to deserialize it.
var response = httpClient.GetAsync(requestUrl);
var json = await response.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RootObject>(json);
Once you have that RootObject model (which is a raw API response deserialized into an object), you can use the aforementioned Automapper to create OnlineOrderHeader and OnlineOrderLine View Models (simplify/flatten).
I have the following WebCleint to call a Restful web service inside my .net console application:-
try
{
using (WebClient wc = new WebClient())
{
wc.Encoding = Encoding.UTF8;
string url = "https://*****/paged?hapikey=*********&properties=website&properties=i_scan&limit=2";//web service url
string tempurl = url.Trim();
var json = wc.DownloadString(tempurl);//get the json
Marketing ipfd = JsonConvert.DeserializeObject<Marketing>(json);//deserialize
}
}
catch (Exception e)
{
//code goes here..
}
where i am using JSON.Net to Deserialize the json object, which will be as follow:-
{
"has-more": true,
"offset": 622438650,
"companies": [
{
"portalId": *******,
"companyId": *****,
"isDeleted": false,
"properties": {
"website": {
"value": "****.net",
"timestamp": 1520938239457,
"source": "CALCULATED",
"sourceId": null,
"versions": [
{
"name": "website",
"value": "*****.net",
"timestamp": 1520938239457,
"source": "CALCULATED",
"sourceVid": [
731938234
]
}
]
}
},
"additionalDomains": [],
"stateChanges": [],
"mergeAudits": []
},
{
"portalId": ******,
"companyId": ******,
"isDeleted": false,
"properties": {
"website": {
"value": "****.***.***",
"timestamp": 1512488590073,
"source": "CALCULATED",
"sourceId": null,
"versions": [
{
"name": "website",
"value": "****.***8.****",
"timestamp": 1512488590073,
"source": "CALCULATED",
"sourceVid": []
}
]
},
"i_scan": {
"value": "Yes",
"timestamp": 1543409493459,
"source": "******",
"sourceId": "**************",
"versions": [
{
"name": "i_scan",
"value": "Yes",
"timestamp": 1543409493459,
"sourceId": *****",
"source": "CRM_UI",
"sourceVid": [],
"requestId": "******"
}
]
}
},
"additionalDomains": [],
"stateChanges": [],
"mergeAudits": []
}
]
}
Here are my classes:-
public class Marketing
{
public Companies companies { get; set; }
}
public class Companies
{
public IList<string> companyId { get; set; }
public IList<Properties> properties { get; set; }
}
public class Properties
{
public IList<Website> website { get; set; }
public IList<I_Scan> i_scan { get; set; }
}
public class Website
{
public string value { get; set; }
}
public class i_Scan
{
public string value { get; set; }
}
but currently i am getting this exception, when i try to de-serialize the JSON object:-
Newtonsoft.Json.JsonSerializationException was caught
HResult=-2146233088
Message=Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'MMarketing.Companies' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'companies', line 1, position 49.
Source=Newtonsoft.Json
StackTrace:
so i am not sure why JSON.NET is unable to do the Deserialize correctly, as in my case the classes are compatible with the returned json object??
At a first glance it looks like you switched two properties in Making them a List and vice versa.
public class Marketing
{
public List<Companies> companies { get; set; }
}
Is "companies": [ in the json, while "companyId": *****, is the id as a string, not array. Properties is not an array also, but the property versions of properties is.
public class Companies
{
public string companyId { get; set; }
public Properties properties { get; set; }
}
If I'm coming to json blind I like to use http://json2csharp.com/ to generate my class structure for me
public class Version
{
public string name { get; set; }
public string value { get; set; }
public object timestamp { get; set; }
public string source { get; set; }
public List<object> sourceVid { get; set; }
}
public class Website
{
public string value { get; set; }
public object timestamp { get; set; }
public string source { get; set; }
public object sourceId { get; set; }
public List<Version> versions { get; set; }
}
public class Version2
{
public string name { get; set; }
public string value { get; set; }
public long timestamp { get; set; }
public int sourceId { get; set; }
public string source { get; set; }
public List<object> sourceVid { get; set; }
public int requestId { get; set; }
}
public class IScan
{
public string value { get; set; }
public long timestamp { get; set; }
public int source { get; set; }
public int sourceId { get; set; }
public List<Version2> versions { get; set; }
}
public class Properties
{
public Website website { get; set; }
public IScan i_scan { get; set; }
}
public class Company
{
public int portalId { get; set; }
public int companyId { get; set; }
public bool isDeleted { get; set; }
public Properties properties { get; set; }
public List<object> additionalDomains { get; set; }
public List<object> stateChanges { get; set; }
public List<object> mergeAudits { get; set; }
}
public class Marketing
{
public bool has_more { get; set; }
public int offset { get; set; }
public List<Company> companies { get; set; }
}
var result = JsonConvert.DeserializeObject<Marketing>(json);
I have some json text which i would like to use to populate a gridview. I can get it working if i dont have the headers part of it in the json data but if i do i get an error. Can someone please help me, not sure where im going wrong
Error reading string. Unexpected token: StartObject. Path '[0].headers'
ASP.NET CODE
public class Emails
{
public string status { get; set; }
public string delivered_at { get; set; }
public string sender { get; set; }
public string email_ts { get; set; }
public string email_id { get; set; }
public string host { get; set; }
public string process_status { get; set; }
public string smtpcode { get; set; }
public string recipient { get; set; }
public string response { get; set; }
public string headers { get; set; }
}
List<Emails> myDeserializedObjList = (List<Emails>)Newtonsoft.Json.JsonConvert.DeserializeObject(strResult, typeof(List<Emails>));
gvRecords.DataSource = myDeserializedObjList;
gvRecords.DataBind();
JSON VALUE
[
{
"status": "ok",
"delivered_at": "2014-02-12T20:51:48.000059+00:00",
"sender": "abc#123.co.nz",
"headers": {
"subject": "Test No 1"
},
"email_ts": "2014-02-12T20:51:46.219800+00:00",
"email_id": "1WDgmY-4gfM00-Hj",
"host": "mx1.webhost.co.nz [119.47.119.2]",
"process_status": "completed",
"smtpcode": 250,
"recipient": "bob#123.co.nz",
"response": "250 2.0.0 Ok: queued as 8022160F4F"
},
{
"status": "hardbounce",
"delivered_at": "2014-02-12T20:55:32.000047+00:00",
"sender": "jim#123.co.nz",
"headers": {
"subject": "Test No 1"
},
"email_ts": "2014-02-12T20:55:30.028400+00:00",
"email_id": "1WDgqA-4gfLik-2I",
"host": "mx1.webhost.co.nz [119.47.119.2]",
"process_status": "completed",
"smtpcode": 550,
"recipient": "womble#123.co.nz",
"response": "550 5.1.1 <womble#123.co.nz>: Recipient address rejected: User unknown in virtual mailbox table"
}
]
There are some mistakes formatting your JSON data
First , You should use Angel Braces [ after the "header" property like that :
"headers":[ {
"subject": "Test No 1"
}],
Second : In your model class , you defined smtpcode as a string property while you passed it an int value inside your JSON data
"smtpcode": 250
It should be :
"smtpcode" : "250"
OR
public int smtpcode {get;set;} and keep it the same in JSON
I am using Linq to SQL in a Web API web service to retrieve data from a database and return a JSON file.
My question is actually pretty simple, but I have been through the forums and couldn't find an answer. Please find below the description of my issue, as well as my (simplified) sourcecode.
The objects I return have two levels of data. To make you understand, here is how my classes look like :
public class X
{
public int ID { get; set; }
public DateTime date { get; set; }
public virtual ICollection<Y> Ys
public virtual ApplicationUser User { get; set; }
}
public class Y
{
public int ID { get; set; }
public int XID { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public virtual X x { get; set; }
}
You can see that for each X1 object, I can have several X2 objects nested.
To retrieve that, I use the following Linq to SQL in my WebAPI Controller :
public IHttpActionResult GetXsByUser(string userID)
{
var xs = (from x in db.Xs
where x.User.Id == userID
orderby x.date
select new
{
x_id = x.ID,
date = x.date,
Ys = (from y in db.Ys
where x.User.Id == userID && x1.ID == y.XID
select new
{
unit_price = y.Price,
quantity = y.Quantity
})
});
if (xs == null)
{
return NotFound();
}
return Ok(xs);
}
My web service works fine and returns the following JSON :
[
{
"$id": "1",
"x_id": 1,
"date": "2014-01-24T00:00:00",
"Ys": [
{
"$id": "2",
"unit_price": 2.47,
"quantity": 2
},
{
"$id": "3",
"unit_price": 1.25,
"quantity": 3
},
{
"$id": "4",
"unit_price": 1.99,
"quantity": 2
}
]
},
{
"$id": "5",
"x_id": 2,
"date": "2014-01-28T00:00:00",
"Ys": [
{
"$id": "6",
"unit_price": 6.22,
"quantity": 1
},
{
"$id": "7",
"unit_price": 1.2,
"quantity": 3
}
]
}
]
The problem is, to then deserialize this in my mobile app, I have to use classes as follows :
public class Y
{
public string _$id { get; set; }
public double unit_price { get; set; }
public int quantity { get; set; }
}
public class RootObject
{
public string _$id { get; set; }
public int x_id { get; set; }
public string date { get; set; }
public List<Y> Ys { get; set; }
}
But i would like to be able to use classes as follow :
public class Y
{
public string _$id { get; set; }
public double unit_price { get; set; }
public int quantity { get; set; }
}
public class OnlineX
{
public string _$id { get; set; }
public int x_id { get; set; }
public string date { get; set; }
public List<Y> Ys { get; set; }
}
public class RootObject
{
public List<OnlineX> OnlineXs { get; set; }
}
I have worked with a JSON editor and know that the solution to get this is to have the following JSON file instead of the previous one :
{
"OnlineXs": [
{
"$id": "1",
"x_id": 1,
"date": "2014-01-24T00:00:00",
"Ys": [
{
"$id": "2",
"unit_price": 2.47,
"quantity": 2
},
{
"$id": "3",
"unit_price": 1.25,
"quantity": 3
},
{
"$id": "4",
"unit_price": 1.99,
"quantity": 2
}
]
},
{
"$id": "5",
"x_id": 2,
"date": "2014-01-28T00:00:00",
"Ys": [
{
"$id": "6",
"unit_price": 6.22,
"quantity": 1
},
{
"$id": "7",
"unit_price": 1.2,
"quantity": 3
}
]
}
]
}
Notice that the only thing that changes is that I add a title to my array of Xs ("Online Xs"). That is why I said that my question is simple. But the thing is, I have no idea how to do that in Web API. Is it just a small change in my Linq to SQL request? Should i build a custom JSON serializer?
I hope that my question is clear enough, and if you want some more information, I'll be happy to provide them.
Thanks a lot in advance
EDIT :
Ok, I've found the solution, it was simple indeed. Here it is :
I had to replace :
return Ok(xs);
by
return Ok(new { OnlineXs = xs });
Just to rephrase your answer, when you return the IHttpActionResult, just assign the query result to a named property and return it like:
return Ok(new { OnlineXs = xs});
I am using JSON.NET (Newtonsoft.Json) to deserialize my JSON into a .NET object - here's my JSON string:
{
"SafetyReport": {
"SafetyData": [{
"Unsafe": "YES",
"CategoryName": "Body Mechanics",
"CategoryData": "Grip / Force",
"Safe": "NO"
}, {
"Unsafe": "YES",
"CategoryName": "Position of People",
"CategoryData": "Falling",
"Safe": "NO"
}, {
"Unsafe": "YES",
"CategoryName": "Position of People",
"CategoryData": "Other",
"Safe": "YES"
}],
"SafeActsObserved": "APPLE",
"UnsafeActsObserved": "OK",
"Date": "11 / 11 / 1988",
"ObserverName": "Bob",
"ObserverGroup": "TEST",
"LocationAreaRegion": "Nowhere",
"Email": "abc#abc.com"
}
}
Here's my C# code - please note that the jsonData string contains exactly the JSON above, just in a single line. I've already verified this:
Once I step past the deserialization, here's what's in my SafetyReport object:
Finally, here are my class definitions for SafetyReport and SafetyData:
public class SafetyReport
{
IList<SafetyData> SafetyData { get; set; }
string SafeActsObserved { get; set; }
string UnsafeActsObserved { get; set; }
string Date { get; set; }
string ObserverName { get; set; }
string ObserverGroup { get; set; }
string LocationAreaRegion { get; set; }
string Email { get; set; }
}
public class SafetyData
{
string Unsafe { get; set; }
string Safe { get; set; }
string CategoryName { get; set; }
string CategoryData { get; set; }
}
QUESTION: What am I doing wrong?
Ok I got it working, multiple things might have been wrong, here's what I did:
Added public modifier to every field.
Removed Safetyreport from JSON
Changed all doublequotes(") to quotes (')
Try this:
class Program
{
static void Main(string[] args)
{
string json = #"{
'SafetyData': [{
'Unsafe': 'YES',
'CategoryName': 'Body Mechanics',
'CategoryData': 'Grip / Force',
'Safe': 'NO'
}, {
'Unsafe': 'YES',
'CategoryName': 'Position of People',
'CategoryData': 'Falling',
'Safe': 'NO'
}, {
'Unsafe': 'YES',
'CategoryName': 'Position of People',
'CategoryData': 'Other',
'Safe': 'YES'
}],
'SafeActsObserved': 'APPLE',
'UnsafeActsObserved': 'OK',
'Date': '11 / 11 / 1988',
'ObserverName': 'Bob',
'ObserverGroup': 'TEST',
'LocationAreaRegion': 'Nowhere',
'Email': 'abc#abc.com'
}
";
SafetyReport sr = JsonConvert.DeserializeObject<SafetyReport>(json);
Console.ReadLine();
}
}
public class SafetyReport
{
public IList<SafetyData> SafetyData { get; set; }
public string SafeActsObserved { get; set; }
public string UnsafeActsObserved { get; set; }
public string Date { get; set; }
public string ObserverName { get; set; }
public string ObserverGroup { get; set; }
public string LocationAreaRegion { get; set; }
public string Email { get; set; }
}
public class SafetyData
{
public string Unsafe { get; set; }
public string Safe { get; set; }
public string CategoryName { get; set; }
public string CategoryData { get; set; }
}
Maybe the problem is the first field of the JSON, you haven't to set witch class you will deserialize, you have to set the attribute names to identify them. Try with this json.
{
"SafetyData": [{
"Unsafe": "YES",
"CategoryName": "Body Mechanics",
"CategoryData": "Grip / Force",
"Safe": "NO"
}, {
"Unsafe": "YES",
"CategoryName": "Position of People",
"CategoryData": "Falling",
"Safe": "NO"
}, {
"Unsafe": "YES",
"CategoryName": "Position of People",
"CategoryData": "Other",
"Safe": "YES"
}],
"SafeActsObserved": "APPLE",
"UnsafeActsObserved": "OK",
"Date": "11 / 11 / 1988",
"ObserverName": "Bob",
"ObserverGroup": "TEST",
"LocationAreaRegion": "Nowhere",
"Email": "abc#abc.com"
}
Hope it helps.