c# parsing azure policy rule json to make a tree - c#

I wanted to parse the JSON with Newtonsoft.Json.Linq into to a tree format by going to each root level.
The actual problem i am facing is the content inside the allOf is not getting printed and its getting InvalidCast exception in JObject. I need help to print all the parent and child elements in the console application.
Here is the JSON:
{
"properties": {
"displayName": "Audit if Key Vault has no virtual network rules",
"policyType": "Custom",
"mode": "Indexed",
"description": "Audits Key Vault vaults if they do not have virtual network service endpoints set up. More information on virtual network service endpoints in Key Vault is available here: _https://learn.microsoft.com/en-us/azure/key-vault/key-vault-overview-vnet-service-endpoints",
"metadata": {
"category": "Key Vault",
"createdBy": "",
"createdOn": "",
"updatedBy": "",
"updatedOn": ""
},
"parameters": {},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.KeyVault/vaults"
},
{
"anyOf": [
{
"field": "Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id",
"exists": "false"
},
{
"field": "Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id",
"notLike": "*"
},
{
"field": "Microsoft.KeyVault/vaults/networkAcls.defaultAction",
"equals": "Allow"
}
]
}
]
},
"then": {
"effect": "audit"
}
}
},
"id": "/subscriptions/xxxxxx/providers/Microsoft.Authorization/policyDefinitions/wkpolicydef",
"type": "Microsoft.Authorization/policyDefinitions",
"name": "xyz"
}
And my code:
static JmesPath jmes = new JmesPath();
static void Main(string[] args)
{
string policyStr = "JSON GIVEN IN THE DESCRIPTION";
string str = jmes.Transform(policyStr, "properties.policyRule.if");
Convert(str);
}
public static void Convert(string json)
{
dynamic myObj = JsonConvert.DeserializeObject(json);
PrintObject(myObj, 0);
Console.ReadKey();
}
private static void PrintObject(JToken token, int depth)
{
if (token is JProperty)
{
var jProp = (JProperty)token;
var spacer = string.Join("", Enumerable.Range(0, depth).Select(_ => "\t"));
var val = jProp.Value is JValue ? ((JValue)jProp.Value).Value : "-";
Console.WriteLine($"{spacer}{jProp.Name} -> {val}");
foreach (var child in jProp.Children())
{
PrintObject(child, depth + 1);
}
}
else if (token is JObject)
{
foreach (var child in ((JObject)token).Children())
{
PrintObject(child, depth + 1);
}
}
}
I have installed JMESPath.Net NuGet package. Demo fiddle here.

Your basic problem is that, in PrintObject(JToken token, int depth), you do not consider the case of the incoming token being a JArray:
if (token is JProperty)
{
}
else if (token is JObject)
{
}
// Else JArray, JConstructor, ... ?
Since the value of "allOf" is an array, your code does nothing:
{
"allOf": [ /* Contents omitted */ ]
}
A minimal fix would be to check for JContainer instead of JObject, however, this does not handle the case of an array containing primitive values, and so cannot be considered a proper fix. (Demo fiddle #1 here.)
Instead, in your recursive code you need to handle all possible subclasses of JContainer including JObject, JArray, JProperty and (maybe) JConstructor. However, the inconsistency between JObject, which has two levels of hierarchy, and JArray which has only one, can make writing such recursive code annoying.
One possible solution to processing arrays and objects in a cleaner manner would be to hide the existence of JProperty entirely and represent that objects are containers whose children are indexed by name while arrays are containers whose children are indexed by integers. The following extension method does this job:
public interface IJTokenWorker
{
bool ProcessToken<TConvertible>(JContainer parent, TConvertible index, JToken current, int depth) where TConvertible : IConvertible;
}
public static partial class JsonExtensions
{
public static void WalkTokens(this JToken root, IJTokenWorker worker, bool includeSelf = false)
{
if (worker == null)
throw new ArgumentNullException();
DoWalkTokens<int>(null, -1, root, worker, 0, includeSelf);
}
static void DoWalkTokens<TConvertible>(JContainer parent, TConvertible index, JToken current, IJTokenWorker worker, int depth, bool includeSelf) where TConvertible : IConvertible
{
if (current == null)
return;
if (includeSelf)
{
if (!worker.ProcessToken(parent, index, current, depth))
return;
}
var currentAsContainer = current as JContainer;
if (currentAsContainer != null)
{
IList<JToken> currentAsList = currentAsContainer; // JContainer implements IList<JToken> explicitly
for (int i = 0; i < currentAsList.Count; i++)
{
var child = currentAsList[i];
if (child is JProperty)
{
DoWalkTokens(currentAsContainer, ((JProperty)child).Name, ((JProperty)child).Value, worker, depth+1, true);
}
else
{
DoWalkTokens(currentAsContainer, i, child, worker, depth+1, true);
}
}
}
}
}
Then your Convert() method now becomes:
class JTokenPrinter : IJTokenWorker
{
public bool ProcessToken<TConvertible>(JContainer parent, TConvertible index, JToken current, int depth) where TConvertible : IConvertible
{
var spacer = new String('\t', depth);
string name;
string val;
if (parent != null && index is int)
name = string.Format("[{0}]", index);
else if (parent != null && index != null)
name = index.ToString();
else
name = "";
if (current is JValue)
val = ((JValue)current).ToString();
else if (current is JConstructor)
val = "new " + ((JConstructor)current).Name;
else
val = "-";
Console.WriteLine(string.Format("{0}{1} -> {2}", spacer, name, val));
return true;
}
}
public static void Convert(string json)
{
var root = JsonConvert.DeserializeObject<JToken>(json);
root.WalkTokens(new JTokenPrinter());
}
Demo fiddle #2 here, which outputs:
allOf -> -
[0] -> -
field -> type
equals -> Microsoft.KeyVault/vaults
[1] -> -
anyOf -> -
[0] -> -
field -> Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id
exists -> false
[1] -> -
field -> Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id
notLike -> *
[2] -> -
field -> Microsoft.KeyVault/vaults/networkAcls.defaultAction
equals -> Allow
Related:
How to do recursive descent of json using json.net?
How to recursively populate a TreeView with JSON data
Iterate over a json input and create the treeview like hierarchy based on the "keys" taking account of the children "keys"

Related

Remove "Integer" properties from JSON Object?

JSON Object:
{
"Footer": "footer",
"RowType": 4,
"answers": [
{
"answer": 1,
"FooterInner": "innerfooter"
},
{
"answer": 2,
"FooterInner": "innerfooter2"
}
]
}
I need to remove all "integer" properties from JSON. JSON object may differ every time. So, consider that we do not know property key names.
Expected JSON Object:
{
"Footer": "",
"answers": [
{
"FooterInner": "innerfooter"
},
{
"FooterInner": "innerfooter2"
}
]
}
The above JSON object is just an example. JSON objects may differ every time (the user uploads JSON objects from UI) and I don't know the hierarchy and key/property names in JSON in advance. And JSON may contain N-nested properties.
I tried a lot things, but couldn't achieve the solution. Is there any way to do it?
You'll have to adjust (and CHECK) the RegEx for your needs but this method will strip out the integers from the json string.
private string CleanJson(string json)
{
var regEx = new Regex("(\"\\w*\": \\d*,)");
var jsonWithoutIntegers = regEx.Replace(json, string.Empty);
return jsonWithoutIntegers;
}
A cleaner way may be writing it up as an extension method.
public static class Extensions
{
public static JToken RemoveFieldTypes(this JToken token,params JTokenType []fieldTypes)
{
JContainer container = token as JContainer;
if (container == null) return token;
var tokensToRemove = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if(p!=null && fieldTypes.Contains(p.Value.Type))
{
tokensToRemove.Add(el);
}
el.RemoveFieldTypes(fieldTypes);
}
foreach (JToken el in tokensToRemove)
{
el.Remove();
}
return token;
}
}
Now you can do the following.
JToken nodeList = JToken.Parse(strJson);
nodeList.RemoveFieldTypes(JTokenType.Integer);
Here is the solution:
static void Main(string[] args)
{
var json =
#"{
""Footer"": ""footer"",
""RowType"": 4,
""answers"":
[
{
""answer"": 1,
""FooterInner"": ""innerfooter""
},
{
""answer"": 2,
""FooterInner"": ""innerfooter2""
}
]
}";
JToken nodeList = JToken.Parse(json);
List<JTokenType> typesToRemove = new List<JTokenType>(){JTokenType.Boolean, JTokenType.Float, JTokenType.Integer};
removeFields(nodeList, typesToRemove);
Console.WriteLine(nodeList.ToString());
Console.ReadKey();
}
private static void removeFields(JToken token, List<JTokenType> typesToRemove)
{
JContainer container = token as JContainer;
if (container == null) return;
List<JToken> removeList = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if (p != null && typesToRemove.Contains(p.Value.Type))
{
removeList.Add(el);
}
removeFields(el, typesToRemove);
}
foreach (JToken el in removeList)
{
el.Remove();
}
}
I am just sharing my first working code, and also for extension method solution you can use #Anu Visman's answer(I didn't try it).

C# get all property conditionally from dynamic collection (which looks like tree)

let's say I have a dynamic collection, which consists set of key-values.
for example:
[
{
key: test1,
value: [
{
key:c1level1,
value: [
{
key:settings-key,
value: c1level2Val
},
{
key: types,
value: typesVal
}
]
}
]
},
{
key: test2,
value: [
{
key:c2level1,
value: [
{
key:settings-key,
value: c2level2Val
},
{
key: types,
value: typesVal
}
]
}
]
}
]
and I want to get all the objects which have a key named "settings-key" and "types" and save them together in a List for example.
How can I achieve this?
I tried this but not works for this solution. when it goes deep in one node, the other stays outside and is never iterated.
for example, when iteration is started and let's say it's on "test1" it ignores "test2" node and works arround only for "test1" node. which is already a problem:
foreach (var item in yaml)
{
if (item.Key == "settings-key" || item.Key == "types")
{
Type typeOfYaml = yaml.GetType();
string placeholderName = String.Empty;
bool placeholderNameExist = typeOfYaml.GetProperties().Where(p => p.Name.Equals("settings-key")).Any();
if (placeholderNameExist)
{
placeholderName = yaml["settings-key"];
}
else
{
return null;
}
List<object> types = null;
bool typesExist = typeOfYaml.GetProperties().Where(p => p.Name.Equals("types")).Any();
if (typesExist)
types = yaml["types"];
var data = new KeyValue
{
RowKey = placeholderName,
Types = types != null ? types.Select(x=>x.ToString()).ToArray() : null
};
return data;
}
else
{
GetKeyValue(yaml[item.Key]);
}
}

Making a tree stucture out of list of strings in c# (winForms)

I am struggling with some logic for making tree structure out of string that I need to pull from database and add to TreeView control.
For the sake of explanation, i have created a small example in winforms
Let's say I have some list of strings
private List<string> strings;
public Form1()
{
InitializeComponent();
strings = new List<string>
{
"Root1.Parrent1",
"Root1.Parrent2",
"Root1.Parrent3.Child1",
"Root1.Parrent4.Child2",
"Root2.newParrent1.newChild1",
"Root2.newParrent1.newChild2",
"Root2.NewParrent2"
};
}
So I want to distinct every repeated element and make a hierarchy that looks like this, note that I have repeated roots as well as parrents (newparrent1)
main root {
root1( root2(
parrent1 newParrent1(
parrent2 --newChild1
parrent3(--child1) --newChild2)
parrent4(--child2) newParrent2)
}
So far my logic is cartoon, I don't even know where to begin, I don't know how menu sub nodes will one node have, done some research, but couldn't get the answer.
I have created example only for the first item of strings list, just to see how my logic would go.
string[] splitsArray = strings[0].Split('.');
TreeNode node = new TreeNode();
node.Text = splitsArray[0];
//creating sub node
TreeNode subNode = new TreeNode();
subNode.Text = splitsArray[1];
//adding
node.Nodes.Add(subNode);
treeView1.Nodes[0].Nodes.Add(node);
Can't even see how could I put this logic into a loop.
Any suggestion is helpful, even pseudo code. thank you for your time.
Since tagged it as an algorithm question I will provide the algorithm only and leave the coding part as your exercise.
First you make a Dictionary of all the nodes with the key being the node name and value as the node object
Dictionary<string, Node> nodes;
and a parent table
Dictionary<string, string> childParent;
then you iterate thru all the strings and split the parts, for each part you check if the name exists in the nodes Dictionary, if not then add them in
and add the parent to the parent table. For example
Root2.newParrent1.newChild1
You will add (newParrent1, Root2) and (newChild1, newParrent1) to the childParent table
Now you have a dictionary of the nodes and parent, we can start making the dependency.
you go thru the childParent table we just created, and add the node to their corresponding parent's Children property
for example if you have (newParrent1, Root2)
you will be doing nodes["Root2"].Children.Add(nodes["newParrent1"]);
Oh, did we forget about the root node?
I will leave that as exercise too.
public class Program
{
public static void Main(string[] args)
{
//Your code goes here
Console.WriteLine("Hello, world!");
List<string> strings;
strings = new List<string>()
{
"Root1.Parrent1",
"Root1.Parrent2",
"Root1.Parrent3.Child1",
"Root1.Parrent4.Child2",
"Root2.newParrent1.newChild1",
"Root2.newParrent1.newChild2",
"Root2.NewParrent2"
};
eat e = new eat(strings);
Console.WriteLine(e.root.ToJson());
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
}
public class eat
{
public node root;
public eat(List<string> l)
{
root = new node("root");
foreach(string s in l)
{
addRow(s);
}
}
public void addRow(string s)
{
List<string> l = s.Split('.').ToList<String>();
node state = root;
foreach(string ss in l)
{
addSoon(state, ss);
state = getSoon(state, ss);
}
}
private void addSoon(node n, string s)
{
bool f = false;
foreach(node ns in n.soon)
{
if (ns.name == s) { f = !f; }
}
if (!f) { n.soon.Add(new node(s)); }
}
private node getSoon(node n,string s)
{
foreach (node ns in n.soon)
{
if (ns.name == s) { return ns; }
}
return null;
}
}
public class node
{
public node(string n)
{
name = n;
soon = new List<node>();
}
public string name;
public List<node> soon;
public string ToJson()
{
String s = "";
s = s + "{\"name\":\"" + name + "\",\"soon\":[";
bool f = true;
foreach(node n in soon)
{
if (f) { f = !f; } else { s = s + ","; }
s = s + n.ToJson();
}
s = s + "]}";
return s;
}
}
this is a solution without the use of dictionaries
we have an eater that has the task of processing and stacking node-like objects
then we have the node that deals with managing the element and the children.
he class tojson is used to verify correct operation
outpot:
{
"name": "root",
"soon": [
{
"name": "Root1",
"soon": [
{
"name": "Parrent1",
"soon": [
]
},
{
"name": "Parrent2",
"soon": [
]
},
{
"name": "Parrent3",
"soon": [
{
"name": "Child1",
"soon": [
]
}
]
},
{
"name": "Parrent4",
"soon": [
{
"name": "Child2",
"soon": [
]
}
]
}
]
},
{
"name": "Root2",
"soon": [
{
"name": "newParrent1",
"soon": [
{
"name": "newChild1",
"soon": [
]
},
{
"name": "newChild2",
"soon": [
]
}
]
},
{
"name": "NewParrent2",
"soon": [
]
}
]
}
]
}
UPDATE
I've just added recursive method into node class that will return TreeNode obj so I can put in TreeView, thank you , this is very useful
public TreeNode GetRoot(List<node> nodes, TreeNode parrent)
{
foreach (var node in nodes)
parrent.Nodes.Add(GetRoot(node.children, new TreeNode(node.name)));
return parrent;
}
!!!!--- Steve's solution is more correct, using dictionaries ---!!!
If you are looking for a way to add those strings to TreeView you can use following code. The answer is trying to do it with minimal code:
treeView1.BeginUpdate();
foreach (var s in strings)
{
TreeNode node = null;
foreach (var text in s.Split('.'))
{
var key = (node == null ? $"{text}" : $"{node.Name}.{text}");
var nodes = (TreeNodeCollection)((dynamic)node ?? (dynamic)treeView1).Nodes;
node = (nodes.Find(key, false)).FirstOrDefault() ?? nodes.Add(key, text);
}
}
treeView1.EndUpdate();
The reason for using dynamic in above code, is because both TreeView and TreeNode have Nodes collection. You can use dynamic or you can have two different branches of code (if(node == null){...}/else{...}) which are quiet similar.
Just for your information TreeNodes collection has a Find method which allows you to find a node by key. The answer relies on setting key for the node when adding which allows us to find it later simply.

How to deserialise a JSON object where the object structure is not known

Part of my code serializes file paths of a machine into JSON in the below format. I am struggling to take this JSON and put the file paths back together again. I am using Newtonsoft JSON lib; I find it's excellent for building JSON. As you can see, my JSON has nested objects.
The JSON I have:
{
".": {
"proc": {
"15": {
"task": {
"15": {
"exe": {},
"mounts": {
"list_of_files": [
"mounts.xml"
]
},
"mountinfo": {
"list_of_files": [
"mountinfo.xml"
]
},
"clear_refs": {
"list_of_files": [
"clear_ref.xml"
]
}
}
}
},
"14": {
"loginuid": {
"list_of_files": [
"loginuid.xml"
]
},
"sessionid": {
"list_of_files": [
"sessionid.xml"
]
},
"coredump_filter": {
"list_of_files": [
"coredump_filter.xml"
]
},
"io": {
"list_of_files": [
"io.xml"
]
}
}
}
}
}
The array I want to generate from this.
string[] dirArray = {
"./proc/15/task/15/exe",
"./proc/15/task/15/mounts/mounts.xml",
"./proc/15/task/15/mountinfo/mountinfo.xml",
"./proc/15/task/15/clear_refs/clear_ref.xml",
"./proc/14/loginuid/loginuid.xml",
"./proc/14/sessionid/sessionid.xml",
"./proc/14/coredump_filter/coredump_filter.xml",
"./proc/14/io/io.xml"
}
My efforts so far-- I deserialised the JSON into a dynamic variable but I'm not sure how to handle two issues:
My JSON format is unknown, I don't know how deep the objects go, how can I handle this?
How do I work with dynamic variables when they are defined at run-time?
EDIT
Sorry, my original JSON format was wrong, so it doesn't work with the answer provided by user12864. I'm getting an error: Unable to cast object of type 'Newtonsoft.Json.Linq.JArray' to type 'Newtonsoft.Json.Linq.JObject'.
Here is a fiddle showing where I'm at so far.
#user12864 has the right idea in his answer, but the code needs to be adjusted to account for the fact that each directory can have an array of files rather a single "file" object (you really should have mentioned that in your question originally). Here is an updated method to handle that:
private static void AddToFileList(JObject jo, List<string> list, string prefix)
{
foreach (var kvp in jo)
{
if (kvp.Key == "list_of_files")
{
foreach (string name in (JArray)kvp.Value)
{
list.Add(prefix + name);
}
}
else
{
JObject dir = (JObject)kvp.Value;
if (dir.Count == 0)
{
list.Add(prefix + kvp.Key);
}
else
{
AddToFileList(dir, list, prefix + kvp.Key + "/");
}
}
}
}
Full demo:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
class Program
{
static void Main(string[] args)
{
string json = #"
{
""."": {
""proc"": {
""15"": {
""task"": {
""15"": {
""exe"": {},
""mounts"": {
""list_of_files"": [
""mounts.xml""
]
},
""mountinfo"": {
""list_of_files"": [
""mountinfo.xml""
]
},
""clear_refs"": {
""list_of_files"": [
""clear_ref.xml""
]
}
}
}
},
""14"": {
""loginuid"": {
""list_of_files"": [
""loginuid.xml""
]
},
""sessionid"": {
""list_of_files"": [
""sessionid.xml""
]
},
""coredump_filter"": {
""list_of_files"": [
""coredump_filter.xml""
]
},
""io"": {
""list_of_files"": [
""io.xml""
]
}
}
}
}
}";
JObject jo = JObject.Parse(json);
foreach (string path in CreateFileList(jo))
{
Console.WriteLine(path);
}
}
private static List<string> CreateFileList(JObject jo)
{
List<string> ret = new List<string>();
AddToFileList(jo, ret, "");
return ret;
}
private static void AddToFileList(JObject jo, List<string> list, string prefix)
{
foreach (var kvp in jo)
{
if (kvp.Key == "list_of_files")
{
foreach (string name in (JArray)kvp.Value)
{
list.Add(prefix + name);
}
}
else
{
JObject dir = (JObject)kvp.Value;
if (dir.Count == 0)
{
list.Add(prefix + kvp.Key);
}
else
{
AddToFileList(dir, list, prefix + kvp.Key + "/");
}
}
}
}
}
Output:
./proc/15/task/15/exe
./proc/15/task/15/mounts/mounts.xml
./proc/15/task/15/mountinfo/mountinfo.xml
./proc/15/task/15/clear_refs/clear_ref.xml
./proc/14/loginuid/loginuid.xml
./proc/14/sessionid/sessionid.xml
./proc/14/coredump_filter/coredump_filter.xml
./proc/14/io/io.xml
Fiddle: https://dotnetfiddle.net/r8CkI2
This should give exactly what you're looking for; just create a JObject with JObject.Parse and pass it to CreateFileList. It won't handle malformed JSON in any nice way.
static List<string> CreateFileList(JObject j)
{
List<string> ret = new List<string>();
AddToFileList(j, ret, "");
return ret;
}
static void AddToFileList(JObject j, List<string> dest, string prefix)
{
if (prefix.Length != 0)
prefix = prefix + '/';
foreach (var kvp in j)
{
var jnext = (JObject)kvp.Value;
if (kvp.Key == "file")
dest.Add(prefix + (string)jnext["name"]);
else
AddToFileList(jnext, dest, prefix + kvp.Key);
}
}
Fiddle at https://dotnetfiddle.net/dQQ4tI
Update:
Here is a revised answer, after you clarified your requirement of:
The JavaScript Object Notation is built on the server, edited by user through a hierarchical tree interface component. That can be crawled incredibly easy.
So in essence your utilizing a component, in which your hoping to build simple JavaScript Object Notation derived from the component. Your User Interface will be unknown, so I'll make some presumptions.
Build our Object:
public class XmlPath
{
public string Location { get; set; }
}
The XmlPath will represent our object. Which will be basic auto property.
Add Content to our Object:
private List<XmlPath> AddXmlPath(List<string> location)
{
List<XmlPath> content = new List<XmlPath>();
foreach(string item in location)
content.Add(new XmlPath() { Location = item });
return content;
}
This will be incredibly simple method, it will take a large List<string> of your user data and add them to your XmlPath object.
Remove content from our Object:
private List<XmlPath> RemoveXmlPath(List<XmlPath> root, string location)
{
root.Remove(new XmlPath() { Location = location });
return root;
}
Those two methods truly don't need to be, I'm just demonstrating and showing how you could. Plus it will outline the intentions a bit easier for you to implement. Please note this is incredibly crude approach.
Serialize / Deserialize Our Object to JavaScript Objection Notation:
JavaScriptSerializer serializer = new JavaScriptSerializer();
var xmlPath = AddXmlPath(List<string> location);
var result = serializer.Serialize(xmlPath);
var deserialize = serializer.Deserialize(List<XmlPath>>(result);
Our content is exposed now through a basic loop:
foreach(XmlPath item in deserialize)
{
// Exposed Model via 'item.Location'
}
You'll simply need to correlate this core functionality to your implementation. The approach is crude, quite rudimentary, definitely will need to be improved on for production. However this should get you started with:
Serialize data on the server.
Deserialize server data.
Manipulating the object.
Hope this is better for you.

Serialize dictionary as array (of key value pairs)

Json.Net typically serializes a Dictionary<k,v> into a collection;
"MyDict": {
"Apples": {
"Taste": 1341181398,
"Title": "Granny Smith",
},
"Oranges": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
},
}
Which is great. And from looking around on SO it seems to be what most people want. However, in this particular case, I want to serialize between my Dictionary<k,v> and the Array format instead;
"MyDict": [
"k": "Apples",
"v": {
"Taste": 1341181398,
"Title": "Granny Smith",
}
},
"k:": "Oranges",
"v:": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
}
},
]
Is there an easy way to do this with my existing field type? Is there an attribute I can annotate for instance?
Another way to accomplish this is to use a custom ContractResolver. That way you do not have to subclass Dictionary<K,V> nor apply a transform each time you serialize, as suggested in other answers.
The following resolver will cause ALL dictionaries to be serialized as an array of objects with "Key" and "Value" properties:
class DictionaryAsArrayResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (objectType.GetInterfaces().Any(i => i == typeof(IDictionary) ||
(i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))))
{
return base.CreateArrayContract(objectType);
}
return base.CreateContract(objectType);
}
}
To use the resolver, add it to your JsonSerializerSettings, then pass the settings to JsonConvert.SerializeObject() like this:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new DictionaryAsArrayResolver();
string json = JsonConvert.SerializeObject(obj, settings);
Here is a working demo.
Ah, it turns out this is as straightforward as I'd hoped. My Dictionary<k,v> is subclassed already and I found that I can annotate it with [JsonArrayAttribute]. That gives me exactly the format I need;
"MyDict": [
{
"Key": "Apples",
"Value": {
"Taste": 1341181398,
"Title": "Granny Smith",
}
},
{
"Key:": "Oranges",
"Value:": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
}
},
]
For this example, I'll use the dictonary:
var myDict = new Dictionary<string,string>() {
{"a","123"},
{"b","234"},
{"c","345"}
};
which serializes (with Newtonsoft.Json.JsonConvert.SerializeObject(myDict)) to:
{"a":"123","b":"234","c":"345"}
You could do a transform using LINQ to create an anonymous object, and serialize that:
var myDictTransformed = from key in myDict.Keys
select new { k = key, v = myDict[key] };
Or you could use a real object
class MyDictEntry
{
public string k { get; set; }
public string v { get; set; }
}
and either the above or the alternative LINQ syntax:
var myDictTransformed = myDict.Keys.AsEnumerable()
.Select(key => new MyDictEntry{
k = key,
v = myDict[key]
});
Either way, this serializes to:
[
{"k":"a", "v":"123"},
{"k":"b", "v":"234"},
{"k":"c", "v":"345"}
]
.NET Fiddle link: https://dotnetfiddle.net/LhisVW
The simplest solution I found is to convert your Dictionary<string, string> to a List<KeyValuePair<string, string>>. JSON.NET then converts your List into an array of objects with the form { Key: 'keyname', Value: 'value' }. This works well if you accept the required model change and don't want to subclass your Dictionary.
gregmac's answer was helpful, but didn't quite work. The following is the same idea... without the puns.
var dictionaryTransformed = dictionary.Select(item => item.Key).Select(i =>
new {Key = i, Value = dictionary[i] });
or of course
var dictionaryTransformed = dictionary.Select(item =>
new {item.Key, Value = dictionary[item.Key] });
Then to json
var json = (new JavaScriptSerializer()).Serialize(
new { Container = dictionaryTransformed.ToArray() } )
I'm not exactly sure why, but the custom ContractResolver by Brian Rogers listed above didn't work for me. It seemed to get into an endless loop somewhere internally. Possibly due to other parts of my json.net setup.
Anyway - this workaround did the trick for me.
public interface IStrongIdentifier
{
string StringValue { get; set; }
}
public class StrongIdentifierKeyedDictionaryWrapper<TKey, TValue> : Dictionary<string, TValue>
where TKey : IStrongIdentifier
{
public void Add(TKey key, TValue value)
{
base.Add(key.StringValue, value);
}
public void Remove(TKey key)
{
base.Remove(key.StringValue);
}
public TValue this[TKey index]
{
get => base[index.StringValue];
set => base[index.StringValue] = value;
}
public bool ContainsKey(TKey key)
{
return base.ContainsKey(key.StringValue);
}
}

Categories

Resources