Как преобразовать иерархические пары значений ключей из строки в json с помощью c#?


У меня есть следующее тело http post, отправленное в asp.net web api через веб-хук от 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

Как преобразовать эту полезную нагрузку[клиент][адрес]=значение и т. д. в строку json, использующую c#?

1   2   2015-05-14 19:20:52

1 ответ:

Ваша текущая проблема

Как конвертировать chargify webhooks в json с помощью c#?

Можно обобщить на

Как извлечь пары значений ключей из строки, преобразовать их в соответствующую иерархию и вернуть в JSON?

Чтобы ответить на ваш вопрос:

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();

С указанными вами входными данными результат выглядит так (без разрывов строк):

{
    "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": "a@test.com",
                                   "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"
                               }
               }
}

Поскольку ваша проблема не ограничивается 1, 2 или 3 уровнями, вы явно нужно рекурсивное решение. Поэтому я создал класс JSONNode, который может вставлять дочерние элементы, указывая иерархию как List<string>.

Если взять в качестве примера A.B.C, то в начале метод InsertIntoHierarchy проверяет, нужны ли дополнительные уровни (в зависимости от длины указанных записей, в нашем случае мы получим список, содержащий A, B и C), если это так, то он вставляет дочерний элемент (используемый в качестве контейнера) с указанным name уровнем и передает задачу дальше к этому ребенку. Конечно, имя текущего уровня рекурсии удаляется во время этого шага, поэтому в нашем примере контейнер с name A был бы добавлен и список, содержащий B и C, был бы передан этому контейнеру. Если достигнут последний уровень рекурсии, то будет вставлен узел, содержащий name и value.

Чтобы заставить решение работать, вам понадобятся следующие 2 классы:

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();
    }
}