Deserialize JSON into any object - c#

I have an API request that goes off and the response structure back looks like this:
{
"MessageBody": {
"foo" : ""
}
}
The properties under MessageBody can be anything, not only foo, but its value is always a string.
eg. {"MessageBody": { "Token": "abc" }} or {"MessageBody": { "Name": "abc" }}
How can I capture this response from the API as a generic object for the property under MessageBody?
I can represent the first example above as:
public class MessageBody
{
public string Token { get; set; }
}
How would I represent both Token or Name properties under the same MessageBody object? There's a bunch of different values that MessageBody can have, but again they would all be of type string.

I have acheived something similar using Newtonsoft
Your route should take the body in as a generic object and it can then be deserialized into any object you'd like:
/*Using this method, the controller will automatically handle
validating proper Json format*/
[HttpPost]
public async Task<IActionResult> Post([FromBody] object Body)
{
/*here you will send the generic object to a service which will deserialize.
the object into an expected model.*/
customService.HandlePost(Body);
}
Now create an object with any expected fields you would get from the body. (Json2csharp.com is extremely useful!)
public class MessageBody
{
public string Token { get; set; }
public string Name { get; set; }
}
Inside your service you can handle the object like this:
using Newtonsoft.Json
using Models.MessageBody
public class customService()
{
public void HandlePost(object body)
{
var DeserializedBody = JsonConvert.DeserializeObject<MessageBody>(body);
//Any Values that were not assigned will be null in the deserialized object
if(DeserializedBody.Name !== null)
{
//do something
}
}
}
This is obviously a very bare bones implementation, error handling will be important to catch any invalid data. Instead of using one object and null fields to get the data you need, I would recommend adding a "subject" route variable (string) that you can use to determine which object to deserialize the body into.
post *api/MessageBody/{Subject}

Related

Problem with json deserialization using refit

I'm using Refit in a project and i am trying to deserialize a json file which is not 'normalized' so to speak.
Here is the link to view an example, you can use ids 1491710 or 254700.
Json Example from Steam
The problem is that the object that is in the root, always changes according to the id that you send in the request, thus making the json unpradonized, like:
{
"dynamic property": {
"success": true,
"data": {
//i want the data here
}
}}
To be honest, I was able to do this, using a regular http request and going down to the json level to get the data object. example:
var jsonData = JToken.Parse(httpResult).First.First;
return jsonData["data"].ToObject<SteamAppResponse>();
But the problem here is that it is necessary to use the Refit library to maintain the design pattern.
My Refit interface:
public interface IStoreApiWebClient
{
[Get("/appdetails?appids={appId}")]
Task<SteamStoreAppResponse> GetAppDetails([Query][AliasAs("appId")] string appId);
}
My Response object:
public class SteamStoreAppResponse
{
[JsonProperty("success")]
public string Success { get; set; }
[JsonProperty("data")]
public SteamApp Data { get; set; }
}

How to post a dynamic JSON property to a C# ASP.Net Core Web API using MongoDB?

How to post a dynamic JSON property to a C# ASP.Net Core Web API using MongoDB?
It seems like one of the advantages of MongoDB is being able to store anything you need to in an Object type property but it doesn't seem clear how you can get the dynamic property info from the client ajax post through a C# Web API to MongoDB.
We want to allow an administrator to create an Event with Title and Start Date/Time but we also want to allow the user to add custom form fields using Reactive Forms for whatever they want such as t-shirt size or meal preference... Whatever the user may come up with in the future. Then when someone registers for the event, they post EventID and the custom fields to the Web API.
We can have an Event MongoDB collection with _id, event_id, reg_time, and form_fields where form_fields is an Object type where the dynamic data is stored.
So we want to POST variations of this JSON with custom FormsFields:
Variation 1:
{
"EventId": "595106234fccfc5fc88c40c2",
"RegTime":"2017-07-21T22:00:00Z",
"FormFields": {
"FirstName": "John",
"LastName": "Public",
"TShirtSize": "XL"
}
}
Variation 2:
{
"EventId": "d34f46234fccfc5fc88c40c2",
"RegTime":"2017-07-21T22:00:00Z",
"FormFields": {
"Email": "John.Public#email.com",
"MealPref": "Vegan"
}
}
I would like to have an EventController with Post action that takes a custom C# EventReg object that maps to the JSON above.
EventController:
[HttpPost]
public void Post([FromBody]EventReg value)
{
eventService.AddEventRegistration(value);
}
EventReg Class:
public class EventReg
{
public EventReg()
{
FormFields = new BsonDocument();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string EventRegId { get; set; }
[BsonElement("EventId")]
[BsonRepresentation(BsonType.ObjectId)]
public string EventId { get; set; }
[BsonElement("reg_time")]
public DateTime RegTime
{
set; get;
}
[BsonElement("form_fields")]
public MongoDB.Bson.BsonDocument FormFields { get; set; }
}
EventService
public string AddEventRegistration(EventReg eventReg)
{
this.database.GetCollection<EventReg>("event_regs").InsertOne(eventReg);
return eventReg.EventRegId;
}
Right now, if I post to the controller, my EventReg is null because it must not know how to map my JSON FormFields properties to a BsonDocument.
What type can I use for FormFields?
Can I have the FormFields property be a BsonDocument and is there an easy way to map the Web API parameter to that?
Is there an example of how some custom serializer might work in this case?
We could maybe use a dynamic type and loop through the posted properties but that seems ugly. I have also seen the JToken solution from a post here but that looks ugly also.
If MongoDB is meant to be used dynamically like this, shouldn't there be a clean solution to pass dynamic data to MongoDB? Any ideas out there?
In ASP.NET Core 3.0+ Newtonsoft.Json is not the default JSON serializer anymore. Therefore I would use JsonElement:
[HttpPost("general")]
public IActionResult Post([FromBody] JsonElement elem)
{
var title = elem.GetProperty("title").GetString();
...
The JToken example works to get data in but upon retrieval it causes browsers and Postman to throw an error and show a warning indicating that content was read as a Document but it was in application/json format. I saw the FormFields property being returned as {{"TShirtSize":"XL"}} so maybe double braces was a problem during serialization.
I ended up using the .NET ExpandoObject in the System.Dynamic namespace. ExpandoObject is compatible with the MongoDB BsonDocument so the serialization is done automatically like you would expect. So no need for weird code to manually handle the properties like the JToken example in the question.
I still believe that a more strongly typed C# representation should be used if at all possible but if you must allow any JSON content to be sent to MongoDB through a Web API with a custom C# class as input, then the ExpandoObject should do the trick.
See how the FormFields property of EventReg class below is now ExpandoObject and there is no code to manually handle the property of the object being saved to MongoDB.
Here is the original problematic and overly complex JToken code to manually populate an object with standard type properties and a dynamic FormFields property:
[HttpPost]
public void Post([FromBody]JToken token)
{
if (token != null)
{
EventReg eventReg = new EventReg();
if (token.Type == Newtonsoft.Json.Linq.JTokenType.Object)
{
eventReg.RegTime = DateTime.Now;
foreach (var pair in token as Newtonsoft.Json.Linq.JObject)
{
if (pair.Key == "EventID")
{
eventReg.EventId = pair.Value.ToString();
}
else if (pair.Key == "UserEmail")
{
eventReg.UserEmail = pair.Value.ToString();
}
else
{
eventReg.FormFields.Add(new BsonElement(pair.Key.ToString(), pair.Value.ToString()));
}
}
}
//Add Registration:
eventService.AddEventRegistration(eventReg);
}
}
Using ExpandoObject removes the need for all of this code. See the final code below. The Web API controller is now 1 line instead of 30 lines of code. This code now can insert and return the JSON from the Question above without issue.
EventController:
[HttpPost]
public void Post([FromBody]EventReg value)
{
eventService.AddEventRegistration(value);
}
EventReg Class:
public class EventReg
{
public EventReg()
{
FormFields = new ExpandoObject();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string EventRegId { get; set; }
[BsonElement("event_id")]
[BsonRepresentation(BsonType.ObjectId)]
public string EventId { get; set; }
[BsonElement("user_email")]
public string UserEmail { get; set; }
[BsonElement("reg_time")]
public DateTime RegTime{ get; set; }
[BsonElement("form_fields")]
public ExpandoObject FormFields { get; set; }
}
EventService:
public string AddEventRegistration(EventReg eventReg)
{
this.database.GetCollection<EventReg>("event_regs").InsertOne(eventReg);
return eventReg.EventRegId;
}
If you are not sure about the type of Json you are sending i.e. if you are dealing with dynamic json. then the below approach will work.
Api:
[HttpPost]
[Route("demoPath")]
public void DemoReportData([FromBody] JObject Jsondata)
Http client call from .net core app:
using (var httpClient = new HttpClient())
{
return await httpClient.PostAsJsonAsync(serviceUrl,
new demoClass()
{
id= "demoid",
name= "demo name",
place= "demo place"
};).ConfigureAwait(false);
}
You can define FormFields as a string and send data to the API in string format after converting JSON to string:
"{\"FirstName\":"John\",\"LastName\":\"Public\",\"TShirtSize\":\"XL\"}"
Then in your controller parse the string to BsonDocument
BsonDocument.Parse(FormFields);
I would use AutoMapper to automate the conversion between the dto and the document

Receiving arbitrary JSON object in MVC method

I have a C# view class such as this:
public class DataObject
{
public int Number { get; set; }
public dynamic Data { get; set; } // <-----
}
being used in an MVC method like this
[HttpPost]
public ActionResult SaveData(DataObject request) {}
The problem is that I want to recieve multiple types of objects in the Data property of the DataObject class.
That is, I want both these to work as valid input json objects.
Type 1
{
Number: 1,
Data: {
Text: "a text"
}
}
Type 2
{
Number: 2,
Data: {
Value: 1,
Options: { 1, 2, 3, 4 }
}
}
Is there a way of doing this with either dynamic objects or some other type of json library magic (just making the property dynamic did nothing)?
All i want to do is store this data in a SQL column nvarchar field and return at a later time (through Entity Framework).
An alternate solution would be to create a view model for each type of input but as there will be 100's of variants to it creating all these views and the corresponding input methods would be cumbersome to maintain.
Adding more details as per comment request: The method is called through Angular.
pub.Save = function (jsonData) {
return $http(
{
method: "POST",
url: baseURL + "/Save",
data: { request: jsonData}, // tried this for string
// data: jsonData, // original way
timeout: 30000
}
)
.then(function (result) {
return result.data;
});
}
At the server side, DTO class must match with the same property name which the payload is carrying.
public class DataObject
{
public string test { get; set; } // <-----
}
So, your save method remains the same:
[HttpPost]
public ActionResult SaveData(DataObject request) {}
The payload json is in the object request.test but its seralized.
Deseralize it using Json Library.
How is it handling multiple different types of variables?
Deseralize it to a dynamic type as:
dynamic obj = JsonConvert.DeserializeObject(request.test, typeof(object));
//Properties within the obj are checked at run time.
if(obj.Text != null) {
//Do your thing
}
if(obj.Value != null) {
//Do your thing
}
if(obj.Options != null) {
//Do your thing
}
By converting the data to a JSON string on the client side I was able to send it to the string property and thus being able to use the same typed view for all objects.
I ended up doing this when saving the object (I'm using angular on the front end), converting the Json object to a string.
entry.Data = angular.toJson(entryData.Data, false);
And then when getting the json string back from MVC I did this to get it back to a real javascript object.
entry.Data = angular.fromJson(entry.Data);
MVC would not accept the JSON object into the text property without making it into a json string first.
Using the above method I am storing data like this in my database:
"{\"Value\":123,\"Currency\":\"EUR\"}"

ViewModel with dynamic elements

I need to receive the next JSON in .NET
"currentData":
{
"Name": {"system": "wfdss", "canWrite": true },
"DiscoveryDateTime": { "system": "wfdss", "canWrite": true },
"Code": { "system": "code", "canWrite": false },
...
}
This elements are dynamics, it doesn't have default elements, so, how can I define a class doing that following next model:
public class currentData
{
//TODO
//<Data Element Name>: {
//data element system: <STRING of system>,
//the last system to update data element canWrite: <Boolean>
//true if requesting system may edit data element (based on ADS), otherwise false. }, ...
public List<Property> property { get; set; }
}
public class Property
{
public string system { get; set; }
public string canWrite { get; set; }
}
If you need to post dynamic structured Json to controller i have a bad news for you - you can't map it automattically in MVC. MVC model binding mechanism work only with stronly typed collecions - you must know structure.
One of the options that i can suggest you if use FormCollection and manually get values from it:
[HttpPost]
public JsonResult JsonAction(FormCollection collection)
{
string CurrentDataNameSystem = collection["currentData.Name.system"];
// and so on...
return Json(null);
}
Another option is to pass you dynamic json as string and then manually desirialize it:
[HttpPost]
public JsonResult JsonAction(string json)
{
//You probably want to try desirialize it to many different types you can wrap it with try catch
Newtonsoft.Json.JsonConvert.DeserializeObject<YourObjectType>(jsonString);
return Json(null);
}
Anyway my point is - you shouldn't mess with dynamic json unless you really need it in MVC.
I suggest you to creage object type that contain all the passible fields but make it all nullable so you can pass your Json and it will be mapped with Model binding MVC mechanism but some fields will be null.
I think the type format you are getting is an Object with a Dictionary.
So i think you need to Deserialize your Data into this.
public class ContainerObject
{
public Dictionary<String,Property> currentData { get; set; }
}

RestSharp: Converting results

I get the following JSON that I am trying to convert to a business object using RestSharp
{
"valid":true,
"data":[
{
"dealerId":"4373",
"branchId":"4373",
}
]
}
I wish to convert to:
public class Dealer
{
public string dealerId ;
public string branchId;
}
But this fails, though the JSON is fine:
var client = new RestClient("http://www.????.com.au");
var request = new RestRequest(string.Format("service/autocomplete/dealer/{0}/{1}.json", suburb.PostCode, suburb.City.Trim().Replace(" ", "%20")), Method.GET);
var response2 = client.Execute<Dealer>(request);
return response2.Data;
Your business object doesn't match the response JSON you are getting back. If you want your response to serialize, your C# object would look something like
public class DealerResponse
{
public bool valid { get;set; }
List<Dealer> data { get;set; }
}
public class Dealer
{
public string dealerId;
public string branchId;
}
I haven't tested this code, but even though you are only interested in the information in 'data', your response C# objects still need to represent the whole JSON response to serialize correctly.
Hope that helps.

Categories