Create child type during deserialization - c#

I got the following JSON document:
{
"Title": "jkdjdjd",
"Description": "dkfkkdd",
"Actions": [{
"ActionType": "Email",
"Subject": "Bkdfkdk",
"Body": "kddkdkkd"
}, {
"ActionType": "SMS",
"PhoneNumber": "+46333333"
}
]
}
My classes looks like:
public class Trigger
{
public string Name { get; set; }
public string Description { get; set; }
public List<Action> Actions { get; set; }
}
public class Action
{
public string ActionType { get; set; }
}
public class EmailAction : Action
{
public string Subject { get; set; }
public string Body { get; set; }
}
public class SmsAction : Action
{
public string PhoneNumber { get; set; }
}
So what I basically want is to make JSON.NET select type of subclass depending on the name in the "ActionType". I know that JSON.NET supports a special field which can be used to identify the subclass. But I rather let the friendly name control which class to generate.
I've figured out that I should use a CustomCreationConverter<Action> for the selection. But I can't figure out how to read that field without screwing up the actual deserialization.
If it helps, I could use the following layout instead:
public class Action
{
public string ActionType { get; set; }
public ActionData Data { get; set; }
}
public ActionData
{
}
public class EmailData : ActionData
{
public string Subject { get; set; }
public string Body { get; set; }
}
public class SmsData : ActionData
{
public string PhoneNumber { get; set; }
}
i.e. the JSON would be:
{
"Title": "jkdjdjd",
"Description": "dkfkkdd",
"Actions": [{
"ActionType": "Email",
"Data": {
"Subject": "Bkdfkdk",
"Body": "kddkdkkd"
}
}, {
"ActionType": "SMS",
"Data": {
"PhoneNumber": "+46333333"
}
}
]
}

If you are not bothered by having a type property included in the JSON you could use the setting of JsonSerializer TypeNameHandling = TypeNameHandling.Auto.
Otherwise you can create a custom JsonConverter and adding it to the list of converters used during serialization. There is a good example in this post which worked well for me:
http://dotnetbyexample.blogspot.co.uk/2012/02/json-deserialization-with-jsonnet-class.html
EDIT:
Does this not work for you as expected?
Have you tried making the Action base class abstract?
What is it that you tried in terms of the CreationConverter? How exactly does it not work - can you give some details on that?
public class JsonActionConverter : JsonCreationConverter<Action>
{
protected override Action Create(Type objectType, JObject jsonObject)
{
var typeName = jsonObject["ActionType"].ToString();
switch(typeName)
{
case "Email":
return new EmailAction();
case "SMS":
return new SMSAction();
default: return null;
}
}
}

Related

Deserialize Json string with child objects

Got the following structure given:
public class TaskList
{
public string Name { get; set; }
public List<ToDoTask> ToDoTasks { get; set; }
}
public class ToDoTask
{
public string Name { get; set; }
public string Note { get; set; }
public DateTime LastEdit { get; set; }
public bool Finished { get; set; }
}
I'm using System.Text.Json in .NET 5.0 to serialize a List successfully into a json-file:
JsonSerializerOptions serializeOptions = new() { WriteIndented = true };
string json = JsonSerializer.Serialize(taskLists, serializeOptions);
the result looks fine:
{
"TaskLists": [
{
"Name": "List1",
"ToDoTasks": [
{
"Name": "Task1",
"Note": "",
"LastEdit": "2022-04-19T13:05:10.0415588+02:00",
"Finished": false
},
{
"Name": "Task2",
"Note": "",
"LastEdit": "2022-04-19T13:05:13.9269202+02:00",
"Finished": false
}
]
},
{
"Name": "List2",
"ToDoTasks": [
{
"Name": "Task3",
"Note": "",
"LastEdit": "2022-04-19T13:05:18.3989081+02:00",
"Finished": false
},
{
"Name": "Task4",
"Note": "",
"LastEdit": "2022-04-19T13:05:23.0949034+02:00",
"Finished": false
}
]
}
]
}
When I deserialize this json-file, I only got the TaskLists but the ToDoTasks, are empty.
List<TaskList> taskLists = JsonSerializer.Deserialize<List<TaskList>>(json);
What do I have to do, get also the ToDoTask-Childs included into the deserialized objects?
Whenever you cannot figure out your model class, you can use Visual Studio's Edit - Paste Special - Paste JSON as Class to check out.
Your model classes should be like this:
public class Rootobject
{
public Tasklist[] TaskLists { get; set; }
}
public class Tasklist
{
public string Name { get; set; }
public Todotask[] ToDoTasks { get; set; }
}
public class Todotask
{
public string Name { get; set; }
public string Note { get; set; }
public DateTime LastEdit { get; set; }
public bool Finished { get; set; }
}
And you can Deserialize it:
static void Main(string[] args)
{
var query = JsonSerializer.Deserialize<Rootobject>(File.ReadAllText("data.json"));
}
Your json has a root object containing the task list, so List<TaskList> does not represent it correctly. Try:
public class Root
{
public List<TaskList> TaskLists { get; set; }
}
var root = JsonSerializer.Deserialize<Root>(json);

How to create the API like this json data

Here is the API that i would like to create ,
{
"Result": "PASS",
"Device": [
{
"ID": "01",
"State": "abc",
},
{
"ID": "02"
"State": "efg",
},
]
}
i want to create the API by return to ApiResult model, Here is my controller,
[HttpGet("device")]
public async Task<ActionResult<ApiResult>> Device()
{
return new ApiResult();
}
My Api Result Model
public class ApiResult
{
public string Result { get; set; }
public ApiDevice[] Device { get; set; }
}
Another model,
public class ApiDevice
{
public string ID { get; set; }
public string State { get; set; }
}
The result that i get is like this,
{"result":null,"device":null}
How to get the result that i need like i mention in first paragraph?
In your example, when you return the new ApiResult from the controller, you are not initializing the properties Result or Device first. You will need to set both of those properties to the appropriate value before returning the object.
Since you are using an array in ApiResult object, you should probably require that values be passed in via the constructor to avoid resizing or replacing the array.
public class ApiResult
{
public string Result { get; set; }
public ApiDevice[] Device { get; set; }
public ApiResult(string result, ApiDevice[] devices) {
Result = result;
Device = devices;
}
}
Another option, which would get you the same JSON data from the API, is to use a List on the ApiResult object.
public class ApiResult
{
public string Result { get; set; }
public List<ApiDevice> Device { get; set; }
public ApiResult() {
Device = new List<ApiDevice>();
}
}
My personal preference would be for the second option.
Your json is invalid, it should be:
{
"Result": "PASS",
"Device": [
{
"ID": "01",
"State": "abc"
},
{
"ID": "02",
"State": "efg"
}
]
}
Your class should be something like:
public class Device
{
public string ID { get; set; }
public string State { get; set; }
}
public class RootObject
{
public string ApiResult { get; set; }
public List<Device> Device { get; set; }
}
This site is great for transposing json to C#: http://json2csharp.com/

What is the correct class structure in C# to convert this Json into?

I have the following Json below coming from a Rest service and I am trying to deserialize it into a C# object using this code:
var _deserializer = new JsonDeserializer();
var results = _deserializer.Deserialize<Report>(restResponse);
The deserialize method keeps returning null which tells me that my C# object is not structured correctly.
Below is the Json and my latest attempt at the C# definition.
{
"Report": [
{
"ID": "0000014",
"Age": "45",
"Details": [
{
"Status": "Approved",
"Name": "Joe"
},
{
"Status": "Approved",
"Name": "Bill"
},
{
"Status": "Submitted",
"Name": "Scott"
}
]
},
{
"ID": "10190476",
"Age": "40",
"Details": [
{
"Status": "Approved",
"Name": "Scott"
}
]
},
{
"ID": "10217480",
"Age": "40",
"Details": [
{
"Status": "Approved",
"Name": "Scott"
}
]
}
]
}
Here is my C# object:
public class Report
{
public List<WorkItem> Item= new List<WorkItem>();
}
public class WorkItem
{
public string ID { get; set; }
public int Age { get; set; }
public List<Details> Details { get; set; }
}
public class Details
{
public string Status { get; set; }
public string Name { get; set; }
}
Can someone advise what is wrong with my C# object definition to make this json deserialize correctly?
I would recommend using Json2Csharp.com to generate the classes.
public class Detail
{
public string Status { get; set; }
public string Name { get; set; }
}
public class Report
{
public string ID { get; set; }
public string Age { get; set; }
public List<Detail> Details { get; set; }
}
public class RootObject
{
public List<Report> Report { get; set; }
}
Try changing the Report class like so (The class name can be anything, the property must be Report)
public class WorkReport
{
public List<WorkItem> Report;
}
It should be trying to deserialize at the root into a class with an array/list of of workitem objects called Report.
You can try something like this. I have changed List to Dictionary You don't have a class defined at the root level. The class structure needs to match the entire JSON, you can't just deserialize from the middle. Whenever you have an object whose keys can change, you need to use a Dictionary. A regular class won't work for that; neither will a List.
public class RootObject
{
[JsonProperty("Report")]
public Report Reports { get; set; }
}
public class Report
{
[JsonProperty("Report")]
public Dictionary<WorkItem> Item;
}
public class WorkItem
{
[JsonProperty("ID")]
public string ID { get; set; }
[JsonProperty("Age")]
public int Age { get; set; }
[JsonProperty("Details")]
public Dictionary<Details> Details { get; set; }
}
public class Details
{
[JsonProperty("Status")]
public string Status { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
}
Then, deserialize like this:
Report results = _deserializer.Deserialize<Report>(restResponse);

How can you deserialize JSON data in C# (using DataContractJsonSerializer) without knowing all property names?

I have been using the DataContractJsonSerializer to convert data returned from the HubSpot API into strongly-typed objects, but I'm having some trouble with the user profile object.
In this example, I am able to get the Id and IsContact properties, but can't figure out how to get the list of properties since I don't know in advance what those can be. I would like to make Properties a Dictionary but I'm not sure how to do this. I don't care about the versions for each property, just the value.
This is a simplified example of the data that is returned by the API:
{
"vid": 72361,
"is-contact": true,
"properties": {
"city": {
"value": "Burlington",
"versions": [
{
"value": "Burlington",
"source-type": "SALESFORCE",
"source-id": "continuous",
"source-label": null,
"timestamp": 1384319976006,
"selected": false
}
]
},
"country": {
"value": "US",
"versions": [
{
"value": "US",
"source-type": "SALESFORCE",
"source-id": "continuous",
"source-label": null,
"timestamp": 1384319976006,
"selected": false
}
]
},
"company": {
"value": "Bridgeline Digital",
"versions": [
{
"value": "Bridgeline Digital",
"source-type": "SALESFORCE",
"source-id": "continuous",
"source-label": null,
"timestamp": 1384319976006,
"selected": false
}
]
}
}
}
This is the object I am trying to deserialize to:
[DataContract]
public class HubSpotUserProfile
{
[DataMember(Name = "vid")]
public int Id { get; set; }
[DataMember(Name = "is-contact")]
public bool IsContact { get; set; }
[DataMember(Name = "redirect")]
public string RedirectUrl { get; set; }
[DataMember(Name = "properties")]
public Dictionary<string, HubSpotUserProfileProperty> Properties { get; set; }
}
[DataContract]
public class HubSpotUserProfileProperty
{
[DataMember(Name = "value")]
public string Value { get; set; }
}
I call this method to perform the deserialization:
public static T Post<T>(string url, string postData) where T : class
{
string json = Post(url, postData);
if (!String.IsNullOrWhiteSpace(json))
{
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
}
return null;
}
When I do this, no error is thrown, but Properties always has a Count of 0. Any idea on how I can accomplish this goal?
Use JsonObject type for your Properties property. In some very strange case DataContractJsonSerializer doesn't support Dictionary<> type in this case
If JSON.NET is an option then James has recently added ExtensionData support. See http://james.newtonking.com/archive/2013/05/08/json-net-5-0-release-5-defaultsettings-and-extension-data.
public class DirectoryAccount
{
// normal deserialization
public string DisplayName { get; set; }
// these properties are set in OnDeserialized
public string UserName { get; set; }
public string Domain { get; set; }
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData;
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
// SAMAccountName is not deserialized to any property
// and so it is added to the extension data dictionary
string samAccountName = (string)_additionalData["SAMAccountName"];
Domain = samAccountName.Split('\\')[0];
UserName = samAccountName.Split('\\')[1];
}
}
Depending on your chosen package for deserializing objects, your current models will work. We use JSon.Net for this exact purpose with HubSpot.
Here's samples of what we use...
[DataContract]
public class ContactHubSpotModel {
// snip for brevity
[DataMember(Name = "properties")]
public Dictionary<string, ContactProperty> Properties { get; set; }
}
[DataContract]
public class ContactProperty
{
[DataMember(Name = "value")]
public string Value { get; set; }
[DataMember(Name = "versions")]
List<ContactPropertyVersion> Versions { get; set; }
}
[DataContract]
public class ContactPropertyVersion
{
[DataMember(Name = "value")]
public string Value { get; set; }
[DataMember(Name = "source-type")]
public string SourceType { get; set; }
[DataMember(Name = "source-id")]
public string SourceId { get; set; }
[DataMember(Name = "source-label")]
public string SourceLabel { get; set; }
[DataMember(Name = "timestamp")]
public long Timestamp { get; set; }
[DataMember(Name = "selected")]
public bool Selected { get; set; }
}
Then you can dump a copy of your contact output into a file for validation like so...
string contactJson = GetContactString(); // pulls sample data stored in a .txt
ContactHubSpotModel contactModel = JsonConvert.DeserializeObject<ContactHubSpotModel>(contactJson);

Can I control what subtype to use when deserializing a JSON string?

I'm working with Facebook API and it's search method returns a JSON response like this:
{
"data": [
{
"id": "1",
"message": "Heck of a babysitter...",
"name": "Baby eating a watermelon",
"type": "video"
},
{
"id": "2",
"message": "Great Produce Deals",
"type": "status"
}
]
}
I have a class structure similar to this:
[DataContract]
public class Item
{
[DataMember(Name = "id")]
public string Id { get; set; }
}
[DataContract]
public class Status : Item
{
[DataMember(Name = "message")]
public string Message { get; set; }
}
[DataContract]
public class Video : Item
{
[DataMember(Name = "string")]
public string Name { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
}
[DataContract]
public class SearchResults
{
[DataMember(Name = "data")]
public List<Item> Results { get; set; }
}
How can I use correct subclass based on that type attribute?
If you use the
System.Web.Script.Serialization.JavaScriptSerializer
you can use the overload in the constructor to add your own class resolver:
JavaScriptSerializer myserializer = new JavaScriptSerializer(new FacebookResolver());
An example how to implement this can be found here on SO: JavaScriptSerializer with custom Type
But you should replace the
"type": "video"
part to
"__type": "video"
since this is the convention for the class.
Here is an example:
public class FacebookResolver : SimpleTypeResolver
{
public FacebookResolver() { }
public override Type ResolveType(string id)
{
if(id == "video") return typeof(Video);
else if (id == "status") return typeof(Status)
else return base.ResolveType(id);
}
}

Categories

Resources