Defining List of Enum as DTO Class C# - c#

This is my input JSON response
{
"permissions": {
"user": [
"Add",
"Update",
"Delete"
],
"Product": [
"Read",
"Create"
]
}
}
I'm trying to create the DTO class for the same. So far I've created a set of Enum with the available values
PermissionAction Enum
public enum PermissionAction
{
Add,
Update,
Read,
Delete,
Create
}
PermissionItem Enum
public enum PermissionItem
{
User,
Product
}
How to create a DTO Class for the given JSON Response with the available Enums?
I tired like differently like
public class Permissions
{
public List<PermissionAction> User { get; set; }
public List<PermissionItem> Product { get; set; }
}
Which is wrong.
Since the response may vary dynamically. What I mean that is sometimes a User List will not available and sometimes Product is not available.
Like,
{
"permissions": {
"user": [
"Add",
"Update",
"Delete"
]
}
}
I need to support those kinds of response also with my DTO Class.
UPDATE 1:
I think the JSON which I posted above is confusing others. Sorry for that confusion. Actually, my permissions JSON Object is contained List of Permission Item(s) in which each Permission Items itself again having a List of Permission Actions.

It seems like you need to use binary or (i.e. |) customizing the serialization.
Enum can already parse many names:
[Flags] // <--- Don't forget to apply this attribute if you want masks!
public enum A { X, Y }
Enum.TryParse<A>("X, Y", out var flags);
if(flags.HasFlag(A.X) && flags.HasFlag(A.Y))
{
// It'll enter here!
}
Definitively, you need to customize your deserialization so that flags coming as arrays get turned into a comma-separated capitalized flag string.
That is, you'll end up with this class:
public class Permissions
{
public Permissions(A a, B b)
{
A = a;
B = b;
}
public A A { get; }
public B B { get; }
}

You can do this with Newtosoft.Json that you get from NuGet:
void Main()
{
string json = #"{
""permissions"": {
""user"": [
""Add"",
""Update"",
""Delete""
],
""Product"": [
""Read"",
""Create""
]
}
}";
var root = Root.FromJson(json);
Console.WriteLine("C# Object");
Console.WriteLine("Permissions:");
Console.WriteLine("\tUser:[");
foreach (var v in root.Permissions.User)
{
Console.WriteLine($"\t\t{v}");
}
Console.WriteLine("\t]");
Console.WriteLine("\tProduct:[");
foreach (var v in root.Permissions.Product)
{
Console.WriteLine($"\t\t{v}");
}
Console.WriteLine("\t]");
Console.WriteLine("As JSON string");
Console.WriteLine(root.ToJson());
}
[JsonConverter(typeof(StringEnumConverter))]
public enum UserAction {
Add,
Update,
Delete
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ProductAction
{
Read,
Create
}
public partial class Root
{
[JsonProperty("permissions")]
public Permissions Permissions { get; set; }
public static Root FromJson(string json) => JsonConvert.DeserializeObject<Root>(json);
public string ToJson() => JsonConvert.SerializeObject(this);
}
public class Permissions
{
[JsonProperty("user")]
public List<UserAction> User { get; set ; }
[JsonProperty("Product")]
public List<ProductAction> Product { get; set; }
}

Related

Q: How to form LINQ query to access nested attributes within class to display on Blazor WASM application

My apologies for the vast amount of code, but it is necessary for the context of the problem. I am faced with an interesting dilemma that I have not been able to solve. I am trying to access information from model called Repository. Repository contains nested classes and lists, and looks like this:
{
public User User { get; set; }
}
public class User
{
public PinnedItems PinnedItems { get; set; }
}
public class PinnedItems
{
public List<Nodes> Nodes { get; set; }
}
public class Nodes
{
public string Name { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public RepositoryTopics RepositoryTopics { get; set; }
}
public class RepositoryTopics
{
public List<TopicNodes> Nodes { get; set; }
}
public class TopicNodes
{
public Topic Topic { get; set; }
}
public class Topic
{
public string Name { get; set; }
}
I have the following method within a web api controller. It is responsible for grabbing my github repositories using graphql. This method looks like this:
{
var request = new GraphQLHttpRequest
{
Query = #"query($username: String!){
user(login: $username) {
pinnedItems(first: 6, types: REPOSITORY) {
nodes {
... on Repository {
name
description
url
repositoryTopics(first:6){
nodes{
topic{
name
}
}
}
}
}
}
}
}
",
Variables = new
{
username = _configuration.GetSection("GithubUserName").Value
}
};
var graphQlResponse = await CreateClient().SendQueryAsync<Repository>(request);
var repo = new Repository
{
User = graphQlResponse.Data.User
};
return Ok(repo);
}
repo is of type Repository.
This is an example piece of JSON that comes back from testing the controller in swagger.
"pinnedItems": {
"nodes": [
{
"name": "personal-website",
"description": "My personal website",
"url": "https://github.com/personal-website",
"repositoryTopics": {
"nodes": [
{
"topic": {
"name": "blazor-webassembly"
}
},
{
"topic": {
"name": "web-api"
}
},
{
"topic": {
"name": "contentful-api"
}
},
{
"topic": {
"name": "contentful"
}
}
]
}
}
I am accessing the code in my blazor component with the following:
Repository SoftwareRepos = new Repository();
protected async override Task OnInitializedAsync()
{
SoftwareRepos = await graphQLquery.GetRepositories();
}
}
And some example code such as this gets me the list of projects as a name.
#foreach(var name in SoftwareRepos.User.PinnedItems.Nodes.Select(x => x.Name).ToArray())
{
#name
}
PRINTS OUT: name, name, name, name
Ideally I would want something that looks like this:
Project One, Description, URL, html, css, react, javascript (a list of tags)
I am having trouble trying to construct LINQ queries to access this nested information (particularly repositoryTopic -> TopicNodes -> Nodes -> Topics -> Name.
I am seeking advice on how to approach this situation, or maybe some alternative solutions to what I am doing as I suspect I am a little out of my depth here. I am using graphql.client to send and retrieve information from github.
first thing to do is to deserialize that JSON into a class structure that it represents.
public class GitResponse{
public Node[] PinnedItems {get;set;}
}
public class Node{
public string Name {get;set};
public string Description {get;set;}
....
}
etc. Once this is done the rest is easy , you just walk that tree
deserialize with
System.Text.Json.Serailizer.Deserialize<GitResponse>(json);

Trying to create model for json

How to create model for this json? I can't understand how to add dictinary to this string array.
{
"ts": 1652718271,
"updates": [
[
4,
508976,
33,
466697301,
1551996353,
"Цацу",
{
"title": " ... ",
"type": "photo"
}
]
]
}
There are a few ways to handle JSON arrays of varied types. One way is to define a class with nullable fields of the types you may encounter in the array. For example,
public class Model
{
public int TS;
public Update[][] Updates;
}
public class Update
{
public int? Number;
public string Word;
public ModelDictionary Dictionary;
}
public class ModelDictionary
{
public string Title;
public string Type;
}
Then you could access each Update with something like
if (Number != null) { ... }
else if (Word != null) { ... }
else if (Dictionary != null) { ... }
Also, https://app.quicktype.io/ is always a great resource for generating C# models from JSON objects.
With this model you can deserialize using Newtonsoft.Json
class Serial
{
public string ts { get; set; }
public object [][] updates { get; set; }
}

Create object from dynamic object

I have a JObject that contains json with the same structure as my Device class:
public class Entry
{
public string key { get; set; }
public object value { get; set; }
}
public class Detail
{
public List<Entry> entry { get; set; }
}
public class Device
{
public List<Detail> details { get; set; }
}
I store the json as dynamic:
dynamic d = JsonConvert.DeserializeObject(json);
I want to create a device object from the dynamic object:
new Device { details = d.details }
I can't simply do like that because details is a list that contains a list, and the list has to be created with a constructor, i can't figure out how to do this. Should be something like?
new Device { details = new List<Detail> ( entry = new List<Entry>(some list from d?))}}
Please advice me.
EDIT:
The Json and my object does not share the same structure, it partly does, so i can't simply convert the while json.
If your JSON has exactly the same structure as Device class, then you should be able to just JsonConvert.DeserializeObject<Device>(jsonString).
Even if the original JSON has some more properties, as long as your classes have the same property names, and what's an array's is an array etc. it should work.
Sample:
public class Entry
{
public string key { get; set; }
public object value { get; set; }
}
public class Detail
{
public List<Entry> entry { get; set; }
}
public class Device
{
public List<Detail> details { get; set; }
}
void Main()
{
var json = #"
{
""irrelevant"": ""fnord"",
""also_irrelevant"": [1,3,5,7],
""details"": [
{
""not_entry"": true,
""entry"": [
{
""key"": ""x"",
""value"": ""1""
},
{
""key"": ""y"",
""value"": ""2""
}
]
},
{
""entry"": [
{
""key"": ""a"",
""value"": ""3"",
""bummer"": ""hello""
},
{
""key"": ""b"",
""value"": ""4"",
""bummer"": ""hello""
}
]
}
]
}";
Newtonsoft.Json.JsonConvert.DeserializeObject<Device>(json).Dump();
}
This runs nicely for me in LINQPad, showing resulting Device class with a list of Detail classes (2 entries), each with a list of Entry classes (2 entries each), with the kvp's set.
If you can, I'd go this route. If for some reason naming of the properties is off and you cannot change it, you can always use [JsonProperty("actual_json_name")] to override it.

Newtonsoft deserialize object that extends from Collection<Item>

I'm trying to use NewtonSoft.Json deserializer, but I don't know if what I'm trying to do is doable, cuase every example of collections that I've seen are like this:
public class ItemRecords
{
public List<ItemRecords> Items { get; set; }
...
}
And what I want is something that looks like as it's explained below...
I have this two classes:
public class ItemRecords : Collection<ItemRecord>
{
[JsonProperty("property1")]
public int Property1 { get; set; }
[JsonProperty("property2")]
public int Property2 { get; set; }
}
public class ItemRecord
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("item_prop1")]
public string ItemProp1 { get; set; }
...
}
I get this json back from my api:
{
property1: 25,
property2: 27,
item_record: [
{
id: 241,
item_prop1: "0.132",
item_prop2: "78787",
...
},
{
id: 242
item_prop1: "1212",
item_prop2: "3333",
...
}
...
]
}
ItemRecords is a collection of ItemRecord.
I tried by adding the JsonArray attribute to the ItemRecords class as follows:
[JsonArray(ItemConverterType = typeof(ItemRecord))]
public class ItemRecords : Collection<ItemRecord> { ... }
Here is the method that executes the request:
private static async Task<T> MakeRequest<T>(string urlRequest)
{
try
{
HttpWebRequest request = WebRequest.Create(urlRequest) as HttpWebRequest;
using (WebResponse response = await request.GetResponseAsync())
{
using (Stream stream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(stream);
string line = string.Empty;
StringBuilder sb = new StringBuilder();
while ((line = reader.ReadLine()) != null)
{
sb.Append(line);
}
T objectDeserialized = JsonConvert.DeserializeObject<T>(sb.ToString());
return objectDeserialized;
}
}
}
catch (Exception ex)
{
return default(T);
}
}
And call to this method looks like this:
...
return await MakeRequest<ItemRecords>(request);
I don't really know what else to do..
Any help would be appreciated.
Your basic difficulty is that the JSON standard has two types of container:
The object which is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Json.NET maps dictionaries and non-enumerable POCOS to objects by default, using reflection to map c# properties to JSON properties.
In the JSON you are getting back from your API, the outermost container is an object.
The array which is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma). Json.NET maps non-dictionary enumerables to arrays by default, serializing each collection item as an array entry.
In the JSON you are getting back from your API, the value of the item_record property is an array.
As a collection with properties, your ItemRecords cannot be mapped to either of these constructs automatically without losing data. Since Json.NET serializes collections as arrays by default, you will need to manually inform it that your type is to be serialized as an object by applying the [JsonObject] attribute. Then, introduce a synthetic item_record property to serialize and deserialize the collection items. Since you are inheriting from Collection<T>, you can use Collection<T>.Items for this purpose:
[JsonObject(MemberSerialization.OptIn)]
public class ItemRecords : Collection<ItemRecord>
{
[JsonProperty("property1")]
public int Property1 { get; set; }
[JsonProperty("property2")]
public int Property2 { get; set; }
[JsonProperty("item_record")]
IList<ItemRecord> ItemRecordList
{
get
{
return Items;
}
}
}
Using MemberSerialization.OptIn prevents base class properties such as Count from being serialized.
Sample fiddle.
As an aside, I don't particularly recommend this design, as it can cause problems with other serializers. For instance, XmlSerializer will never serialize collection properties; see here or here. See also Why not inherit from List?.
Simply add a List<ItemRecord> Records property to class ItemRecords:
public class ItemRecords
{
[JsonProperty("property1")]
public int Property1 { get; set; }
[JsonProperty("property2")]
public int Property2 { get; set; }
[JsonProperty("item_record")]
public List<ItemRecord> Records { get; set; }
}
This looks like you can have a dynamic number of results of properties and item properties like:
{
property1: 25,
property2: 27,
property3: 30,
item_record: [
{
id: 241,
item_prop1: "0.132",
item_prop2: "78787"
},
{
id: 242
item_prop1: "1212",
item_prop2: "3333",
item_prop3: "3333",
item_prop4: "3333",
}
] }
If this is the case, the best option in my opinion will be to change the structure to something like:
{
properties:[
25,
27,
30
],
itemRecords:[
{
id: 27,
itemProperties:[
"321",
"654",
"564"
]
},
{
id: 25,
itemProperties:[
"321",
"654"
]
},
]
}

Accessing subfields in json file

Assuming I have a JSON file with the following structure. How I can access the names of the properties in the metadata field.
{
"mappings": {
"basedoc_12kja": {
"properties": {
"created": {
"type": "date",
"format": "dateOptionalTime"
},
"customerID": {
"type": "string"
},
"deleted": {
"type": "boolean"
},
"documentID": {
"type": "string"
},
"id": {
"type": "string"
},
"metadata": {
"properties": {
"Cert": {
"type": "string"
},
"Exp_date": {
"format": "dateOptionalTime"
},
}
}
}
}
}
}
Mappings is an array of documents, each subfield of mappings has a different code. I want to obtain the metadata fields of each document to find out which metadata fields are common between them.
I haven't been able to instantiate this documents.
var response = esReader.GetIndicesMapping();
foreach (var mapping in response.Response.Values)
{
// Parse JSON into dynamic object, convenient!
dynamic results = JObject.Parse(mapping);
List<DocumentType> deserializedObject = JsonConvert.DeserializeObject<List<DocumentType>>(mapping);
}
Exception
{"Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.List`1[DocumentType]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'mappings', line 2, position 14."}
The desire result is to obtain the name of Cert and Exp_date fields
EDIT
public class DocumentType
{
public string Id { set { DocumentID = value; } get { return DocumentID; } }
public string DocumentID { set; get; }
public DateTime Created { set; get; }
.
.
.
public Dictionary<string, object> Metadata { set; get; }
}
The problem here is that your data structure does not match the JSON:
There are no arrays in the JSON. So there is no way you will be able to deserialize that into a C# List.
The "DocumentType" class doesn't match the JSON at all. The class has properties Created, CustomerID, and Deleted which are things like DateTime and string. But the JSON doesn't have those as DateTime or string. They are objects with subproperties named "type" and "format." The property "Metadata" isn't a dictionary: it is an object with a single property named "properties" which should probably be a dictionary.
The case doesn't match.
Don't do that weird thing with Id and DocumentId. The class should match the JSON exactly and literally. No business logic hidden in properties.
The root object has a property called "mappings" so you will need to drill-down before you get to the documents.
Once you successfully get the document, you will need to drill down to the property named "properties" to get to the fields you are interested in.
I suspect there could be multiple documents, and that the "mappings" property contains a list of those documents, where the property names are dynamic and correspond to the name of the document. It is entirely plausible to handle that but not using a deserialization + List approach.
I see 3 approaches here:
Fix the JSON. Not sure if this is possible in your case. If so, start by making mappings hold an array instead of having each document be a property named by the document name.
Fix the deserialization code to match the JSON document. json2csharp did an excellent job so start with that. It just doesn't know that "mappings" is really a Dictionary, not just a thing with a property named "basedoc12_kja."
Don't deserialize it at all. Just query for the metadata. take a look at http://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm which shows several ways to query JSON using JObject properties and LINQ.
Option 1
Example of a slightly cleaned-up JSON if you go that route:
{
"mappings": [
{
"name"" : "basedoc_12kja",
"properties": {
""created": "20150522",
etc.
},
Notice "mappings" is an array and the name became a property of the document. Now you can make a List<> or use JArray. Even better is to get rid of the unused stuff at the top, like this:
[
{
"name" : "basedoc_12kja",
"properties": {
"created"": "20150522",
etc.
},
]
Now it is just an array with no "mappings" at all.
** Option 2 **
Here is code that will do this via deserialization. There are two parts. Step one is to use what json2charp produced. I'll include that here for reference:
public class Created
{
public string type { get; set; }
public string format { get; set; }
}
public class CustomerID
{
public string type { get; set; }
}
public class Deleted
{
public string type { get; set; }
}
public class DocumentID
{
public string type { get; set; }
}
public class Id
{
public string type { get; set; }
}
public class Cert
{
public string type { get; set; }
}
public class ExpDate
{
public string format { get; set; }
}
public class Properties2
{
public Cert Cert { get; set; }
public ExpDate Exp_date { get; set; }
}
public class Metadata
{
public Properties2 properties { get; set; }
}
public class Properties
{
public Created created { get; set; }
public CustomerID customerID { get; set; }
public Deleted deleted { get; set; }
public DocumentID documentID { get; set; }
public Id id { get; set; }
public Metadata metadata { get; set; }
}
public class Basedoc12kja
{
public Properties properties { get; set; }
}
public class Mappings
{
public Basedoc12kja basedoc_12kja { get; set; }
}
public class RootObject
{
public Mappings mappings { get; set; }
}
Then, rename Basedoc12kja to DocumentType, and change RootObject to hold a dictionary. You get this:
public class DocumentType
{
public Properties properties { get; set; }
}
public class RootObject
{
public Dictionary<string, DocumentType> mappings { get; set; }
}
And if you want to get to properties other than just Cert and Exp_date then change Metadata to this:
public class Metadata
{
public Dictionary<string,object> properties { get; set; }
}
Now that can deserialize your document:
JObject results = JObject.Parse(mapping);
RootObject ro = results.ToObject<RootObject>()
You can enumerate through the mappings and get to the properties. They are still messy because of the JSON structure, but you can at least get there.
I hope this helps!
What you have here is a hierarchical dictionary of named properties, where each property can have a type, a format, and possibly a nested dictionary of named child properties -- metadata in your case. You can represent this with the following data model:
[DataContract]
public class PropertyData
{
[DataMember(Name="type", EmitDefaultValue=false)]
public string Type { get; set; }
[DataMember(Name = "format", EmitDefaultValue = false)]
public string Format { get; set; }
[DataMember(Name = "properties", EmitDefaultValue = false)]
public Dictionary<string, PropertyData> Properties { get; set; }
}
[DataContract]
public class Mappings
{
[DataMember(Name = "mappings", EmitDefaultValue = false)]
public Dictionary<string, PropertyData> DocumentMappings { get; set; }
}
(This data model doesn't capture the fact that a given property (probably) can only be a simple type or a complex type with nested properties - but not both. It would seem to suffice for your needs however.)
Then, given the JSON above, you would read it in and convert it to a dictionary of document name to metadata property name as follows:
var mappings = JsonConvert.DeserializeObject<Mappings>(json);
Debug.WriteLine(JsonConvert.SerializeObject(mappings, Formatting.Indented)); // Verify that all was read in.
var metadataNames = mappings.DocumentMappings.ToDictionary(doc => doc.Key, doc => doc.Value.Properties["metadata"].Properties.Select(p => p.Key).ToList());
Debug.WriteLine(JsonConvert.SerializeObject(metadataNames, Formatting.Indented)); // Inspect the resulting mapping table.
And the result is the dictionary of metadata names you want:
{
"basedoc_12kja": [
"Cert",
"Exp_date"
]
}
If you are concerned that the nested metadata might be missing sometimes and so generate NullReferenceExceptions in the query above, you can add null checks as follows:
// Extension methods to query or walk through nested properties, avoiding null reference exceptions when properties are missing
public static class PropertyDataExtensions
{
public static IEnumerable<KeyValuePair<string, PropertyData>> GetProperties(this PropertyData data)
{
if (data == null || data.Properties == null)
return Enumerable.Empty<KeyValuePair<string, PropertyData>>();
return data.Properties;
}
public static PropertyData GetProperty(this PropertyData data, string name)
{
if (data == null || data.Properties == null)
return null;
PropertyData child;
if (!data.Properties.TryGetValue(name, out child))
return null;
return child;
}
}
And then:
var metadataNamesSafe = mappings.DocumentMappings.ToDictionary(doc => doc.Key, doc => doc.Value.GetProperty("metadata").GetProperties().Select(p => p.Key).ToList());

Categories