Al Polden
Al Polden

Reputation: 920

How to convert hierarchical key value pairs from a string into json with c#?

I have the following http post body sent to a asp.net web api via a web hook from chargify.

id=38347752&event=customer_update&payload[customer][address]=qreweqwrerwq&payload[customer][address_2]=qwerewrqew&payload[customer][city]=ererwqqerw&payload[customer][country]=GB&payload[customer][created_at]=2015-05-14%2004%3A46%3A48%20-0400&payload[customer][email]=a%40test.com&payload[customer][first_name]=Al&payload[customer][id]=8619620&payload[customer][last_name]=Test&payload[customer][organization]=&payload[customer][phone]=01&payload[customer][portal_customer_created_at]=2015-05-14%2004%3A46%3A49%20-0400&payload[customer][portal_invite_last_accepted_at]=&payload[customer][portal_invite_last_sent_at]=2015-05-14%2004%3A46%3A49%20-0400&payload[customer][reference]=&payload[customer][state]=&payload[customer][updated_at]=2015-05-14%2011%3A25%3A19%20-0400&payload[customer][verified]=false&payload[customer][zip]=&payload[site][id]=26911&payload[site][subdomain]=testsubdomain

How do i convert this payload[customer][address]=value etc. to a json string using c#?

Upvotes: 2

Views: 1923

Answers (1)

Markus Safar
Markus Safar

Reputation: 6580

You current problem

How to convert chargify webhooks to json with c#?

can be generalized to

How to extract key value pairs from a string, convert them into the corresponding hierarchy and return them in JSON?

To answer your question:

string rawData = "id=38347752&event=customer_update&payload[customer][address]=qreweqwrerwq&payload[customer][address_2]=qwerewrqew&payload[customer][city]=ererwqqerw&payload[customer][country]=GB&payload[customer][created_at]=2015-05-14%2004%3A46%3A48%20-0400&payload[customer][email]=a%40test.com&payload[customer][first_name]=Al&payload[customer][id]=8619620&payload[customer][last_name]=Test&payload[customer][organization]=&payload[customer][phone]=01&payload[customer][portal_customer_created_at]=2015-05-14%2004%3A46%3A49%20-0400&payload[customer][portal_invite_last_accepted_at]=&payload[customer][portal_invite_last_sent_at]=2015-05-14%2004%3A46%3A49%20-0400&payload[customer][reference]=&payload[customer][state]=&payload[customer][updated_at]=2015-05-14%2011%3A25%3A19%20-0400&payload[customer][verified]=false&payload[customer][zip]=&payload[site][id]=26911&payload[site][subdomain]=testsubdomain";
ChargifyWebHook webHook = new ChargifyWebHook(rawData);
JSONNode node = new JSONNode("RootOrWhatEver");

foreach (KeyValuePair<string, string> keyValuePair in webHook.KeyValuePairs)
{
    node.InsertInHierarchy(ChargifyWebHook.ExtractHierarchyFromKey(keyValuePair.Key), keyValuePair.Value);
}

string result = node.ToJSONObject();

With your specified input the result looks like this (without line breaks):

{
    "id": "38347752",
    "event": "customer_update",
    "payload": {
                   "customer": {
                                   "address": "qreweqwrerwq",
                                   "address_2": "qwerewrqew",
                                   "city": "ererwqqerw",
                                   "country": "GB",
                                   "created_at": "2015-05-14 04:46:48 -0400",
                                   "email": "[email protected]",
                                   "first_name": "Al",
                                   "id": "8619620",
                                   "last_name": "Test",
                                   "organization": "",
                                   "phone": "01",
                                   "portal_customer_created_at": "2015-05-14 04:46:49 -0400",
                                   "portal_invite_last_accepted_at": "",
                                   "portal_invite_last_sent_at": "2015-05-14 04:46:49 -0400",
                                   "reference": "",
                                   "state": "",
                                   "updated_at": "2015-05-14 11:25:19 -0400",
                                   "verified": "false",
                                   "zip": ""
                               },
                       "site": {
                                   "id": "26911",
                                   "subdomain": "testsubdomain"
                               }
               }
}

As your problem is not limited to 1, 2 or 3 levels you clearly need a recursive solution. Therefore I created a JSONNode class which is able to insert children by specifying the hierarchy as a List<string>.

If you take A.B.C as an example, at the beginning the method InsertIntoHierarchy checks whether more levels are needed or not (depending on the length of the entries specified, in our case we would get a list containing A, B and C), if so it inserts a child (used as container) with the specified name of the level and passes the problem on to this child. Of course the name of the current recursion level is removed during that step so according to our example the container with the name A would have been added and the list containing B and C would have been passed on to this container. If the last level of recursion is reached, a node containing the name and the value will be inserted.

To get the solution working you will need the following 2 classes:

ChargifyWebHook

/// <summary>
/// Represents the chargify web hook class.
/// </summary>
public class ChargifyWebHook
{
    /// <summary>
    /// Indicates whether the raw data has already been parsed or not.
    /// </summary>
    private bool initialized;

    /// <summary>
    /// Contains the key value pairs extracted from the raw data.
    /// </summary>
    private Dictionary<string, string> keyValuePairs;

    /// <summary>
    /// Initializes a new instance of the <see cref="ChargifyWebHook"/> class.
    /// </summary>
    /// <param name="data">The raw data of the web hook.</param>
    /// <exception cref="System.ArgumentException">Is thrown if the sepcified raw data is null or empty.</exception>
    public ChargifyWebHook(string data)
    {
        if (String.IsNullOrEmpty(data))
        {
            throw new ArgumentException("The specified value must neither be null nor empty", data);
        }

        this.initialized = false;
        this.keyValuePairs = new Dictionary<string, string>();
        this.RawData = data;
    }

    /// <summary>
    /// Gets the raw data of the web hook.
    /// </summary>
    public string RawData
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets the key value pairs contained in the raw data.
    /// </summary>
    public Dictionary<string, string> KeyValuePairs
    {
        get
        {
            if (!initialized)
            {
                this.keyValuePairs = ExtractKeyValuesFromRawData(this.RawData);
                initialized = true;
            }

            return this.keyValuePairs;
        }
    }

    /// <summary>
    /// Extracts the key value pairs from the specified raw data.
    /// </summary>
    /// <param name="rawData">The data which contains the key value pairs.</param>
    /// <param name="keyValuePairSeperator">The pair seperator, default is '&'.</param>
    /// <param name="keyValueSeperator">The key value seperator, default is '='.</param>
    /// <returns>The extracted key value pairs.</returns>
    /// <exception cref="System.FormatException">Is thrown if an key value seperator is missing.</exception>
    public static Dictionary<string, string> ExtractKeyValuesFromRawData(string rawData, char keyValuePairSeperator = '&', char keyValueSeperator = '=')
    {
        Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();

        string[] rawDataParts = rawData.Split(new char[] { keyValuePairSeperator });

        foreach (string rawDataPart in rawDataParts)
        {
            string[] keyAndValue = rawDataPart.Split(new char[] { keyValueSeperator });

            if (keyAndValue.Length != 2)
            {
                throw new FormatException("The format of the specified raw data is incorrect. Key value pairs in the following format expected: key=value or key1=value1&key2=value2...");
            }

            keyValuePairs.Add(Uri.UnescapeDataString(keyAndValue[0]), Uri.UnescapeDataString(keyAndValue[1]));
        }

        return keyValuePairs;
    }

    /// <summary>
    /// Extracts the hierarchy from the key, e.g. A[B][C] will result in A, B and C.
    /// </summary>
    /// <param name="key">The key who's hierarchy shall be extracted.</param>
    /// <param name="hierarchyOpenSequence">Specifies the open sequence for the hierarchy speration.</param>
    /// <param name="hierarchyCloseSequence">Specifies the close sequence for the hierarchy speration.</param>
    /// <returns>A list of entries for the hierarchy names.</returns>
    public static List<string> ExtractHierarchyFromKey(string key, string hierarchyOpenSequence = "[", string hierarchyCloseSequence = "]")
    {
        if (key.Contains(hierarchyOpenSequence) && key.Contains(hierarchyCloseSequence))
        {
            return key.Replace(hierarchyCloseSequence, string.Empty).Split(new string[] { hierarchyOpenSequence }, StringSplitOptions.None).ToList();
        }

        if (key.Contains(hierarchyOpenSequence) && !key.Contains(hierarchyCloseSequence))
        {
            return key.Split(new string[] { hierarchyOpenSequence }, StringSplitOptions.None).ToList();
        }

        if (!key.Contains(hierarchyOpenSequence) && key.Contains(hierarchyCloseSequence))
        {
            return key.Split(new string[] { hierarchyCloseSequence }, StringSplitOptions.None).ToList();
        }

        return new List<string>() { key };
    }
}

JSONNode

/// <summary>
/// Represents the JSONNode class.
/// </summary>
public class JSONNode
{
    /// <summary>
    /// Initializes a new instance of the <see cref="JSONNode"/> class.
    /// </summary>
    /// <param name="name">The name of the node.</param>
    /// <param name="value">The value of the node.</param>
    public JSONNode(string name, string value)
    {
        this.Name = name;
        this.Value = value;
        this.Children = new Dictionary<string, JSONNode>();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="JSONNode"/> class.
    /// </summary>
    /// <param name="name">The name of the node.</param>
    public JSONNode(string name)
        : this(name, string.Empty)
    {
    }

    /// <summary>
    /// Gets the name of the node.
    /// </summary>
    public string Name
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets the children of the node.
    /// </summary>
    public Dictionary<string, JSONNode> Children
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets the value of the node.
    /// </summary>
    public string Value
    {
        get;
        private set;
    }

    /// <summary>
    /// Inserts a new node in the corresponding hierarchy.
    /// </summary>
    /// <param name="keyHierarchy">A list with entries who specify the hierarchy.</param>
    /// <param name="value">The value of the node.</param>
    /// <exception cref="System.ArgumentNullException">Is thrown if the keyHierarchy is null.</exception>
    /// <exception cref="System.ArgumentException">Is thrown if the keyHierarchy is empty.</exception>
    public void InsertInHierarchy(List<string> keyHierarchy, string value)
    {
        if (keyHierarchy == null)
        {
            throw new ArgumentNullException("keyHierarchy");
        }

        if (keyHierarchy.Count == 0)
        {
            throw new ArgumentException("The specified hierarchy list is empty", "keyHierarchy");
        }

        // If we are not in the correct hierarchy (at the last level), pass the problem
        // to the child.
        if (keyHierarchy.Count > 1)
        {
            // Extract the current hierarchy level as key
            string key = keyHierarchy[0];

            // If the key does not already exists - add it as a child.
            if (!this.Children.ContainsKey(key))
            {
                this.Children.Add(key, new JSONNode(key));
            }

            // Remove the current hierarchy from the list and ...
            keyHierarchy.RemoveAt(0);

            // ... pass it on to the just inserted child.
            this.Children[key].InsertInHierarchy(keyHierarchy, value);
            return;
        }

        // If we are on the last level, just insert the node with it's value.
        this.Children.Add(keyHierarchy[0], new JSONNode(keyHierarchy[0], value));
    }

    /// <summary>
    /// Gets the textual representation of this node as JSON entry.
    /// </summary>
    /// <returns>A textual representaiton of this node as JSON entry.</returns>
    public string ToJSONEntry()
    {
        // If there is no child, return the name and the value in JSON format.
        if (this.Children.Count == 0)
        {
            return string.Format("\"{0}\":\"{1}\"", this.Name, this.Value);
        }

        // Otherwise there are childs so return all of them formatted as object.
        StringBuilder builder = new StringBuilder();
        builder.AppendFormat("\"{0}\":", this.Name);
        builder.Append(this.ToJSONObject());

        return builder.ToString();
    }

    /// <summary>
    /// Gets the textual representation of this node as JSON object.
    /// </summary>
    /// <returns>A textual representaiton of this node as JSON object.</returns>
    public string ToJSONObject()
    {
        StringBuilder builder = new StringBuilder();

        builder.Append("{");

        foreach (JSONNode value in this.Children.Values)
        {
            builder.Append(value.ToJSONEntry());
            builder.Append(",");
        }

        builder.Remove(builder.Length - 1, 1);
        builder.Append("}");

        return builder.ToString();
    }
}

Upvotes: 1

Related Questions