Object to separate KeyValue Pair collections - c#

I have a challenge where I need to take any object and flatten into a key value pair format
This is working really well for simple classes, and even classes where I have other classes within it
Looking at an example,
public class Buyer
{
[JsonProperty("name")]public string Name { get; set; }
[JsonProperty("address")]public string Address { get; set; }
[JsonProperty("lastPurchase")]public Purchase LastPurchase { get; set; }
public Buyer()
{
Name = "Joe Bloggs";
Address = "An adddress somewhere";
AllPurchases = new List<Purchase>()
{
new Purchase() {PurchaseAmount = 100, PurchaseDateTime = Convert.ToDateTime("2017-01-01")},
new Purchase() {PurchaseAmount = 100, PurchaseDateTime = Convert.ToDateTime("2018-01-01")}
};
LastPurchase = new Purchase() {PurchaseAmount = 100, PurchaseDateTime = Convert.ToDateTime("2018-01-01")};
}
[JsonIgnore]
public List<Purchase> AllPurchases { get; set; }
}
public class Purchase
{
public DateTime PurchaseDateTime { get; set; }
public double PurchaseAmount { get; set; }
}
I have the code below which is my current implementation
var buyer = new Buyer();
var json = JsonConvert.SerializeObject(buyer);
var obj = JObject.Parse(json);
var result = obj.Descendants()
.OfType<JProperty>()
.Where(s => s.Value.Type != JTokenType.Object)
.Select(p => new KeyValuePair<string, string>(p.Path,
p.Value.Type == JTokenType.Array || p.Value.Type == JTokenType.Object
? null : p.Value.ToString()));
var serializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
var newJson = JsonConvert.SerializeObject(result, serializerSettings);
Console.WriteLine(newJson);
This generates the Json below which is perfect
[
{
"key": "name",
"value": "Joe Bloggs"
},
{
"key": "address",
"value": "An adddress somewhere"
},
{
"key": "lastPurchase.PurchaseDateTime",
"value": "01/01/2018 00:00:00"
},
{
"key": "lastPurchase.PurchaseAmount",
"value": "100"
}
]
Things get tricky when I introduce serialising the list by removing JsonIgnore
Now I get
[
{
"key": "name",
"value": "Joe Bloggs"
},
{
"key": "address",
"value": "An adddress somewhere"
},
{
"key": "lastPurchase.PurchaseDateTime",
"value": "01/01/2018 00:00:00"
},
{
"key": "lastPurchase.PurchaseAmount",
"value": "100"
},
{
"key": "allPurchases",
"value": null
},
{
"key": "allPurchases[0].PurchaseDateTime",
"value": "01/01/2017 00:00:00"
},
{
"key": "allPurchases[0].PurchaseAmount",
"value": "100"
},
{
"key": "allPurchases[1].PurchaseDateTime",
"value": "01/01/2018 00:00:00"
},
{
"key": "allPurchases[1].PurchaseAmount",
"value": "100"
}
]
This has obviously happened because my logic doesnt have anything specific in it for processing lists
How can I change my logic so that AllPurchases is a key value pair collection with the key being allPurchases[0], allPurchases[1] and the value is separate key value collection, which would avoid key names like allPurchases[0].PurchaseAmount etc?
I need to keep the solution generic so that it will flatten any object into this structure
Paul

From what I can see you want the following.
Values and objects not in the array to be transferred to json objects in form of {key:"propertyPath", value:"valueTostring"}
Sub Objects to arrays of of key value pairs.
Arrays to indexed {key:"property[index]", value:"valueTostringOrObjectKeyValueArray"}
The following
var result = GetItmes(obj);
IEnumerable<KeyValuePair<string,object>> GetItmes(in JToken token, string path = "")
{
return token switch
{
JObject jObject => from prop in token.Children<JProperty>()
from child in GetItmes(prop.Value, string.IsNullOrEmpty(path) ? prop.Name : $"{path}.{prop.Name}")
select child,
JArray jArray => from item in jArray.Select((t, i) => (t, i))
select new KeyValuePair<string, object>($"{path}[{item.i}]",GetItmes(item.t)),
JValue jValue => new[] {
new KeyValuePair<string, object>(path, (object)jValue?.ToString())
},
_ => Enumerable.Empty<KeyValuePair<string, object>>(),
};
}
Will create
[
{
"key": "name",
"value": "Joe Bloggs"
},
{
"key": "address",
"value": "An adddress somewhere"
},
{
"key": "lastPurchase.PurchaseDateTime",
"value": "1/1/2018 12:00:00 AM"
},
{
"key": "lastPurchase.PurchaseAmount",
"value": "100"
},
{
"key": "AllPurchases[0]",
"value": [
{
"key": "PurchaseDateTime",
"value": "1/1/2017 12:00:00 AM"
},
{
"key": "PurchaseAmount",
"value": "100"
}
]
},
{
"key": "AllPurchases[1]",
"value": [
{
"key": "PurchaseDateTime",
"value": "1/1/2018 12:00:00 AM"
},
{
"key": "PurchaseAmount",
"value": "100"
}
]
}
]
This code is recursive and unoptimized I am sure there could be a much more efficient way to do this.
For
IEnumerable<KeyValuePair<string, object>> GetItmes(JToken token, string path = "")
{
switch (token)
{
case JObject jObject:
return from prop in token.Children<JProperty>()
from child in GetItmes(prop.Value, string.IsNullOrEmpty(path) ? prop.Name : $"{path}.{prop.Name}")
select child;
case JArray jArray:
return from item in jArray.Select((t, i) => (t, i))
select new KeyValuePair<string, object>($"{path}[{item.i}]", GetItmes(item.t));
case JValue jValue:
return new[] {
new KeyValuePair<string, object>(path, (object)jValue?.ToString())
};
default: return Enumerable.Empty<KeyValuePair<string, object>>();
};
}

Related

Dictionary<string, JToken> recursive search

I have DeserializeObject to a C# object however I have objects with dynamic object names so I have structured it like this:
public class RootObject
{
public string name { get; set; }
public TableLayout table{ get; set; }
}
public class TableLayout
{
public Attributes attributes { get; set; } //Static
public Info info { get; set; } //Static
[JsonExtensionData]
public Dictionary<string, JToken> item { get; set; }
}
So basically any dynamic objects that appear will be added to the dictionary and using JsonExtensionData will populate the rest of the property without creating the object classes. Here is my json:
string json = #"
{
"name": "Table 100",
"table": {
"attributes ": {
"id": "attributes",
"type": "attributes"
},
"info": {
"id": "info",
"type": "info"
},
"item-id12": {
"id": "item-id12",
"type": "Row"
"index": 0
},
"item-id16": {
"id": "item-id16",
"type": "Column"
"parentid": "item-id12"
},
"item-id21": {
"id": "item-id21",
"type": "Column",
"parentid": "item-id12"
}
}
}";
How can I use type ="row" and index value(increments to index 1 to evaluate next row) property to get all columns using parentId of column objects in my Dictionary.
Desired Output:
"item-id12": {
"id": "item-id12",
"type": "Row"
"index": 0
},
"item-id16": {
"id": "item-id16",
"type": "Column"
"parentid": "item-id12"
},
"item-id21": {
"id": "item-id21",
"type": "Column",
"parentid": "item-id12"
}
You can use linq to find your root object
var rootNode = json.table.item.Values
.FirstOrDefault(x => x["type"].Value<string>() == "Row" && x["index"].Value<int>() == 0);
if (rootNode == null)
return; // no such item
Now if this item exists use linq again and get all items from dictionary:
var childNodes = json.table.item.Values
.Where(x => x["parentid"]?.Value<string>() == rootNode["id"].Value<string>());
Next code
var output = new[] {rootNode}.Concat(childNodes);
foreach (var item in output)
Console.WriteLine(item);
will print
{
"id": "item-id12",
"type": "Row",
"index": 0
}
{
"id": "item-id16",
"type": "Column",
"parentid": "item-id12"
}
{
"id": "item-id21",
"type": "Column",
"parentid": "item-id12"
}
P.S. Your input json is invalid, it missing few commas

How to deserialise JSON from HubSpot

I am having trouble deserializing JSON received from HubSpot ContactList API.
I am using Restsharp and NewtonSoft, and I'm having real struggles understanding how to correctly define the required classes in order to deserialize the JSON string, which is below:
"contacts": [
{
"vid": 2251,
"portal-id": 5532227,
"is-contact": true,
"profile-url": "https://app.hubspot.com/contacts/5532227/contact/2251",
"properties": {
"firstname": {
"value": "Carl"
},
"lastmodifieddate": {
"value": "1554898386040"
},
"company": {
"value": "Cygnus Project"
},
"lastname": {
"value": "Swann"
}
},
"form-submissions": [],
"identity-profiles": [
{
"vid": 2251,
"saved-at-timestamp": 1553635648634,
"deleted-changed-timestamp": 0,
"identities": [
{
"type": "EMAIL",
"value": "cswann#cygnus.co.uk",
"timestamp": 1553635648591,
"is-primary": true
},
{
"type": "LEAD_GUID",
"value": "e2345",
"timestamp": 1553635648630
}
]
}
],
"merge-audits": []
},
{
"vid": 2301,
"portal-id": 5532227,
"is-contact": true,
"profile-url": "https://app.hubspot.com/contacts/5532227/contact/2301",
"properties": {
"firstname": {
"value": "Carlos"
},
"lastmodifieddate": {
"value": "1554886333954"
},
"company": {
"value": "Khaos Control"
},
"lastname": {
"value": "Swannington"
}
},
"identity-profiles": [
{
"vid": 2301,
"saved-at-timestamp": 1553635648733,
"deleted-changed-timestamp": 0,
"identities": [
{
"type": "EMAIL",
"value": "cswann#khaoscontrol.com",
"timestamp": 1553635648578,
"is-primary": true
},
{
"type": "LEAD_GUID",
"value": "c7f403ba",
"timestamp": 1553635648729
}
]
}
],
"merge-audits": []
}
],
"has-more": false,
"vid-offset": 2401
}
If I simply request the vid, I correctly get 2 vid's back. It's when I try to do the properties and that i get a fail.
Please help
Lets reduce the Json to the minimum to reproduce your error :
{
"vid": 2301,
"portal-id": 5532227,
"is-contact": true,
"profile-url": "https://app.hubspot.com/contacts/5532227/contact/2301",
"properties": {
"firstname": {
"value": "Carlos"
},
"lastmodifieddate": {
"value": "1554886333954"
},
"company": {
"value": "Khaos Control"
},
"lastname": {
"value": "Swannington"
}
}
}
And the appropriate class ContactListAPI_Result:
public partial class ContactListAPI_Result
{
[JsonProperty("vid")]
public long Vid { get; set; }
[JsonProperty("portal-id")]
public long PortalId { get; set; }
[JsonProperty("is-contact")]
public bool IsContact { get; set; }
[JsonProperty("profile-url")]
public Uri ProfileUrl { get; set; }
[JsonProperty("properties")]
public Dictionary<string, Dictionary<string, string>> Properties { get; set; }
}
public partial class ContactListAPI_Result
{
public static ContactListAPI_Result FromJson(string json)
=> JsonConvert.DeserializeObject<ContactListAPI_Result>(json);
//public static ContactListAPI_Result FromJson(string json)
// => JsonConvert.DeserializeObject<ContactListAPI_Result>(json, Converter.Settings);
}
public static void toto()
{
string input = #" {
""vid"": 2301,
""portal-id"": 5532227,
""is-contact"": true,
""profile-url"": ""https://app.hubspot.com/contacts/5532227/contact/2301"",
""properties"": {
""firstname"": {
""value"": ""Carlos""
},
""lastmodifieddate"": {
""value"": ""1554886333954""
},
""company"": {
""value"": ""Khaos Control""
},
""lastname"": {
""value"": ""Swannington""
}
}
}";
var foo = ContactListAPI_Result.FromJson(input);
}
But the Value of one property will be burrow in the sub dictionary, we can the project the object in a more usefull one :
public partial class ItemDTO
{
public long Vid { get; set; }
public long PortalId { get; set; }
public bool IsContact { get; set; }
public Uri ProfileUrl { get; set; }
public Dictionary<string, string> Properties { get; set; }
}
Adding the projection to the Class:
public ItemDTO ToDTO()
{
return new ItemDTO
{
Vid = Vid,
PortalId = PortalId,
IsContact = IsContact,
ProfileUrl = ProfileUrl,
Properties =
Properties.ToDictionary(
p => p.Key,
p => p.Value["value"]
)
};
}
Usage :
var result = foo.ToDTO();
Live Demo
Creating and managing class structure for big and nested key/value pair json is tedious task
So one approach is to use JToken instead.
You can simply parse your JSON to JToken and by querying parsed object, you will easily read the data that you want without creating class structure for your json
From your post it seems you need to retrieve vid and properties from your json so try below code,
string json = "Your json here";
JToken jToken = JToken.Parse(json);
var result = jToken["contacts"].ToObject<JArray>()
.Select(x => new
{
vid = Convert.ToInt32(x["vid"]),
properties = x["properties"].ToObject<Dictionary<string, JToken>>()
.Select(y => new
{
Key = y.Key,
Value = y.Value["value"].ToString()
}).ToList()
}).ToList();
//-----------Print the result to console------------
foreach (var item in result)
{
Console.WriteLine(item.vid);
foreach (var prop in item.properties)
{
Console.WriteLine(prop.Key + " - " + prop.Value);
}
Console.WriteLine();
}
Output:

Searching DeserializeObject JSON

My DeserializeObject is listed below.
How can I use Linq (or any other way) to find Value based on Key?
var dictionaryList = JsonConvert.DeserializeObject<dynamic>(response);
{{
"Resources": [
{
"Key": "HeadingCustomerSegments",
"Value": "Customer segments"
},
{
"Key": "Clear all",
"Value": "Clear all"
},
{
"Key": "Third selection of stores the report will be based on",
"Value": "Third selection of stores the report will be based on"
},
{
"Key": "Select the stores to be included in the Dashboard/Opportunity",
"Value": "Select the stores to be included in the Dashboard/Opportunity"
},
}}
In case you want to do it with Linq without a concrete class you could do it this way:
var dictionaryList = (JObject)JsonConvert.DeserializeObject(#"{
""Resources"": [
{
""Key"": ""HeadingCustomerSegments"",
""Value"": ""Customer segments""
},
{
""Key"": ""Clear all"",
""Value"": ""Clear all""
},
{
""Key"": ""Third selection of stores the report will be based on"",
""Value"": ""Third selection of stores the report will be based on""
},
{
""Key"": ""Select the stores to be included in the Dashboard/Opportunity"",
""Value"": ""Select the stores to be included in the Dashboard/Opportunity""
}]
}");
var element = dictionaryList["Resources"]?.FirstOrDefault(x => x["Key"].Value<string>() == "HeadingCustomerSegments");
var value = element != null ? element["Value"]?.Value<string>() : null;
Console.WriteLine(value);
A more proper way, with a concrete class, would be like this:
void Main()
{
var dictionaryList = JsonConvert.DeserializeObject<Response>(#"{
""Resources"": [
{
""Key"": ""HeadingCustomerSegments"",
""Value"": ""Customer segments""
},
{
""Key"": ""Clear all"",
""Value"": ""Clear all""
},
{
""Key"": ""Third selection of stores the report will be based on"",
""Value"": ""Third selection of stores the report will be based on""
},
{
""Key"": ""Select the stores to be included in the Dashboard/Opportunity"",
""Value"": ""Select the stores to be included in the Dashboard/Opportunity""
}]
}");
var value = dictionaryList.Resources.Where(r => r.Key == "HeadingCustomerSegments").Select(r => r.Value).FirstOrDefault();
Console.WriteLine(value);
}
public class Response
{
public List<Resource> Resources { get; set; }
}
public class Resource
{
public string Key { get; set; }
public string Value { get; set; }
}
For response that you've provided you can use:
var listsOfResources = JObject.Parse(response).SelectToken("Resources").ToList();
but you can try also generate dictionary if you want:
var dictionaryResources = JObject.Parse(response).SelectToken("Resources").ToObject<Dictionary<string, string>>();
for getting single item from listsOfResources you can use e.g. ExpandoObject:
var item = JsonConvert.DeserializeObject<ExpandoObject>(listsOfResources[0].ToString());

Form the JSON object - serialization C#

"fields": [
{
"field": {
"name": "SMS",
"value": "Yes"
}
},
{
"field": {
"name": "Email",
"value": ""
}
},
{
"field": {
"name": "Total",
"value": ""
}
},
]
I have tried to form the JSON format like above, so i formed the class like below. While serialization it does not return expected form, how can i achieve this one.
public class Test
{
public List<Field> fields;
}
public class Field
{
public string name { get; set; }
public string value { get; set; }
}
Response:
"fields": [{
"name": "SMS",
"value": "Yes"
}, {
"name": "Email",
"value": ""
},{
"name": "Total",
"value": ""
}]
Use this website http://json2csharp.com and generate all the classes automatically. Just copy-paste your json there.
You can customize resulting JSON object with anonymous types and LINQ. Please try this code:
var test = new Test {fields = new List<Field>()};
test.fields.Add(new Field {name = "f1", value = "v1"});
test.fields.Add(new Field {name = "f2", value = "v2"});
var json = JObject.FromObject(new { fields = test.fields.Select(f => new {field = f}).ToArray() })
.ToString();
A json variable would be:
{
"fields": [
{
"field": {
"name": "f1",
"value": "v1"
}
},
{
"field": {
"name": "f2",
"value": "v2"
}
}
]
}
You just missed a class level:
public class Test
{
public List<FieldHolder> fields;
}
public class FieldHolder
{
public Field field { get; set; }
}
public class Field
{
public string name { get; set; }
public string value { get; set; }
}

Loop Json Properties in C#

I receive the following JSON string. It is part of the specifications of products. The data represents the Header ("General") and the items within in Key value pairs.
I dont want to hard code any properties such as general, Display Features and should be able to
Retrieve the Header
Iterate on the key value pair.
So Far I have the following code
foreach (var SpecList in objAPIProduct.categorySpecificInfoV1.specificationList)
{
foreach(dynamic SpecItem in SpecList.General)
{
Console.WriteLine(SpecItem.key);
Console.WriteLine(SpecItem.value[0]);
}
}
I want to get rid of the "General" Property that I have hardcoded in the inner foreach loop and make the loop generic for all properties.
Here is the Json that I have
{
"General": [{
"key": "Sales Package",
"value": ["Laptop, Power Adapter, Warranty Document"]
},
{
"key": "Model Number",
"value": ["3558"]
},
{
"key": "Part Number",
"value": ["Z565103UIN9"]
},
{
"key": "Model Name",
"value": ["Inspiron 15"]
},
{
"key": "Series",
"value": ["Inspiron"]
},
{
"key": "Color",
"value": ["Black"]
},
{
"key": "Type",
"value": ["Notebook"]
},
{
"key": "Suitable For",
"value": ["Everyday Use"]
},
{
"key": "Battery Backup",
"value": ["Upto 3.5 hours"]
},
{
"key": "Battery Cell",
"value": ["4 cell"]
}]
},
{
"Processor and Memory Features": [{
"key": "Processor Brand",
"value": ["Intel"]
},
{
"key": "Processor Name",
"value": ["Core i3"]
},
{
"key": "Graphic Processor",
"value": ["Intel HD Graphics 5500"]
},
{
"key": "SSD",
"value": ["No"]
},
{
"key": "RAM",
"value": ["4 GB"]
},
{
"key": "RAM Type",
"value": ["DDR3"]
},
{
"key": "HDD Capacity",
"value": ["500 GB"]
},
{
"key": "Processor Variant",
"value": ["5005U"]
},
{
"key": "Clock Speed",
"value": ["2 GHz"]
},
{
"key": "Memory Slots",
"value": ["1 Slot"]
},
{
"key": "Expandable Memory",
"value": ["Upto 4 GB"]
},
{
"key": "RAM Frequency",
"value": ["1600 MHz"]
},
{
"key": "Cache",
"value": ["3 MB"]
},
{
"key": "RPM",
"value": ["5400"]
}]
}
On site json2csharp.com you can generate classes for your JSON.
public class General
{
public string key { get; set; }
public List<string> value { get; set; }
}
public class RootObject
{
public List<General> General { get; set; }
}
And deserialize JSON to it using JSON.NET:
RootObject ro = JsonConvert.DeserializeObject<RootObject>(json);
And:
foreach(General g in ro.General)
{
string k = g.key;
}
Rewrite your SpecList class like this
public class SpecList : Dictionary<string, List<SpecItem>>
{
}
From then you will be able to access your general property like this
foreach (var SpecList in objAPIProduct.categorySpecificInfoV1.specificationList)
{
foreach(var key in SpecList.Keys)
{
foreach(var SpecItem in SpecList[key])
{
Console.WriteLine(SpecItem.key);
Console.WriteLine(SpecItem.value[0]);
}
}
}
Hope it helps.
After trying out a lot of options, I found that it is not possible to get the "Name" of the property when we use Json.Net or JavascriptSerialiser.
Finally, I used the help of this thread -
Deserialize JSON into C# dynamic object?
wherein the person has suggested using System.Web.Helpers and it works perfect in my case.
Here is my final code:
dynamic objAPIProduct= Json.Decode(json);
List<ProductSpecification> objProductSpecificationList = new List<ProductSpecification>();
ProductSpecification objProductSpecification;
foreach (var SpecListCategoryList in objAPIProduct.categorySpecificInfoV1.specificationList)
{
foreach (var SpecCategory in SpecListCategoryList)
{
DynamicJsonArray objListitems = SpecCategory.Value;
for (int i = 0; i < objListitems.Length; i++)
{
objProductSpecification = new ProductSpecification();
objProductSpecification.SpecificationHead = SpecCategory.Key;
objProductSpecification.SpecificationKey = objListitems[i].key;
objProductSpecification.SpecificationValue = objListitems[i].value[0];
objProductSpecificationList.Add(objProductSpecification);
}
}
}

Categories