Преобразование значений полей PHP-форм во вложенные объекты JavaScript

Как известно, чтобы передать в PHP-скрипт данные из HTML-формы в виде массива, нужно использовать ключи вида “field[key]”, например:
[html] <form id=myform>
Пользователь 15:<br>
<input type=hidden name="User[15][id]" value="15"><br>
Имя:<input type=text name="User[15][name]" value="Вася"><br>
Телефон:<input type=text name="User[15][phone]" value="+79991234567"><br>
Пользователь 16:<br>
<input type=hidden name="User[16][id]" value="16"><br>
Имя:<input type=text name="User[16][name]" value="Петя"><br>
Телефон<input type=text name="User[16][phone]" value="+79824445551"><br>
</form>
[/html] Многие PHP-фреймворки при создании HTML-форм, дают имена их элементам подобным образом. Это и понятно, формировать их так – очень просто, при передаче такой формы в PHP-код она превратится в удобный массив вида:

array(
    "User" => array(
        15 => array(
            "name" => "Вася",
            "phone" => "+79991234567"
        ),
        16 => array(
            "name" => "Петя",
            "phone" => "+79824445551"
        ),        
    )
);

Но если вы передаёте его не путём обычной отправки формы, а с помощью Ajax, тут вас и подстерегает проблема.


Допустим, что мы хотим сделать вот такой ajax-запрос:

//В переменной data хотелось бы иметь данные из заполненной формы
$.post("/api/users", {"action":save, "users_data":data}, function(result) {
    alert("Ок");
});

А в PHP-коде мы сможем получить доступ к данным формы, обратившись к $_POST[‘users_data’]. Хотелось бы иметь возможность в PHP обращаться к полям формы таким образом: $name = $_POST[‘users_data’][‘User’][16][‘name’];

Как мы можем получить данные из формы? Даже с помощью jQuery существует как минимум несколько способов:

1. С помощью serialize:

data = $('#myform').serialize();

Эта функция вернёт нам такую строку:

User%5B15%5D%5Bid%5D=15&User%5B15%5D%5Bname%5D=%D0%92%D0%B0%D1%81%D1%8F&User%5B15%5D%5Bphone%5D=%2B79991234567&User%5B16%5D%5Bid%5D=16&User%5B16%5D%5Bname%5D=%D0%9F%D0%B5%D1%82%D1%8F&User%5B16%5D%5Bphone%5D=%2B79824445551

Если передать данные в таком формате, то PHP получит их в виде такой-же строки. (Конечно, распарсить её можно с помощью функции parse_str, однако, у такой строки и ещё один минус – невозможность манипулировать данными в JavaScript перед их отправкой на сервер.

2. С помощью serializeArray:

data = $('#myform').serializeArray();

Эта функция вернёт такой массив объектов:

Object { name="User[15][id]",  value="15"}, Object { name="User[15][name]",  value="Вася"}, Object { name="User[15][phone]",  value="+79991234567"}, Object { name="User[16][id]",  value="16"}, Object { name="User[16][name]",  value="Петя"}, Object { name="User[16][phone]",  value="+79824445551"}

Проблема этого массива в том, что, при передаче его в PHP в составе другого объекта, он не будет правильно разобран. К примеру, если передать эти данные вот таким образом:

$.post("/api/go", {"fields": $('#myform').serializeArray()}, function(result) {alert("ok");});

То в PHP коде окажется, что $_POST массив имеет следующую структуру:

array("fields" => array(
                        "User[16" => "+79824445551",
                 )
)                

Чтобы сформировать вложенный объект как раз и создана данная функция:

    /**
     * Convert html 'nested' field name, such as "field[key1][key2][key3]" 
     * and it's value into nested object.
     * @param {String} field field name, like "field[key1][key2][key3]"
     * @param {String} value fields value, read from value attribute
     * @param {Object} append_to object, where to append resulting sub objects
     *                 (default {})
     * @returns {Object}
     */
    function htmlFieldToObject(field, value, append_to)
    {
        append_to = append_to || {};
        var re_big = /^([^\[\]]+)((\[[^\]\[]+\])*)$/;
        var matches = field.match(re_big);
        var first = matches[1];
        var keys_str = matches[2];
        var keys = [first];
        if (keys_str)
        {
            var re_small = new RegExp(
                "\\[(.+?)\\]",
                "gi"
            );
            while(matches = re_small.exec(keys_str))
            {
                keys.push(matches[1]);
            }
        }
        var obj = append_to;
        var cur = obj;
        var parent = null;
        var lastI = keys.length - 1;
        for (var i in keys)
        {
                var key = keys[i];
            if (i != lastI)
            {
                if (cur[key] === undefined)
                {
                    cur[key] = {};
                }
                var newcur = cur[key];
                cur = null;
                cur = newcur;
            }
            else
            {
                cur[key] = value;
            }
        }
        return obj;
    }

Как ей пользоваться:

//Получаем с помощью serializeArray массив объектов {name: ... , value: ...}, 
//из которых возьмём имена полей и значения
var values = $("#myform").serializeArray();
var obj = {};
for (var i in values)
{
    var field = values[i]["name"];
    var value = values[i]["value"];
    obj = htmlFieldToObject(field, value, obj);
}
console.log(obj);

В результате получим как раз то, чего хотели:

{
    "User": {
        "15": {
            "name": "Вася",
            "phone": "+79991234567"
        },
        "16": {
            "name": "Петя",
            "phone": "+79824445551"
        }
    }
}

So, what do you think ?