JSON.stringify не работает с обычными массивами JavaScript.

Вопрос или проблема

Кажется, я что-то упускаю, но следующий код (Fiddle) возвращает пустую строку:

var test = new Array();
test['a'] = 'test';
test['b'] = 'test b';
var json = JSON.stringify(test);
console.log(json);

Какой правильный способ преобразования этого массива в строку?

Массивы JavaScript предназначены для хранения данных с числовыми индексами. Вы можете добавлять именованные свойства в них, потому что массив — это тип объекта (и это может быть полезно, когда вы хотите сохранить метаданные о массиве, который содержит нормальные, упорядоченные данные с числовыми индексами), но это не то, для чего они предназначены.

Тип данных массива JSON не может иметь именованные ключи в массиве.

Когда вы передаете массив JavaScript в JSON.stringify, именованные свойства будут проигнорированы.

Если вам нужны именованные свойства, используйте объект, а не массив.

const test = {}; // Объект
test.a="test";
test.b = []; // Массив
test.b.push('item');
test.b.push('item2');
test.b.push('item3');
test.b.item4 = "A value"; // Игнорируется JSON.stringify
const json = JSON.stringify(test);
console.log(json);

Хорошее объяснение и пример выше. Я нашел это (Странности JSON.stringify() с Prototype.js) для завершения ответа. Некоторые сайты реализуют свой собственный toJSON с JSONFilters, так что удалите его.

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}

это работает нормально, и вывод теста:

console.log(json);

Результат:

"{"a":"test","b":["item","item2","item3"]}"

Я опубликовал исправление для этого здесь

Вы можете использовать эту функцию, чтобы модифицировать JSON.stringify, чтобы кодировать массивы, просто поместите ее ближе к началу вашего скрипта (см. ссылку выше для получения более подробной информации):

// Обновление для JSON.stringify, обновлено для поддержки массивов
(function(){
    // Преобразовать массив в объект
    var convArrToObj = function(array){
        var thisEleObj = new Object();
        if(typeof array == "object"){
            for(var i in array){
                var thisEle = convArrToObj(array[i]);
                thisEleObj[i] = thisEle;
            }
        }else {
            thisEleObj = array;
        }
        return thisEleObj;
    };
    var oldJSONStringify = JSON.stringify;
    JSON.stringify = function(input){
        if(oldJSONStringify(input) == '[]')
            return oldJSONStringify(convArrToObj(input));
        else
            return oldJSONStringify(input);
    };
})();

Другой подход — это JSON.stringify() функция замещения. Вы можете передать 2-й аргумент в JSON.stringify(), который имеет специальное обращение для пустых массивов, как показано ниже.

const arr = new Array();
arr.answer = 42;

// {"hello":"world","arr":{"answer":42}}
JSON.stringify({ hello: 'world', arr }, function replacer(key, value) {
  if (Array.isArray(value) && value.length === 0) {
    return { ...value }; // Преобразует пустой массив с строковыми свойствами в простой объект
  }
  return value;
});

В качестве альтернативы, вы можете использовать это

var test = new Array();
test[0]={};
test[0]['a'] = 'test';
test[1]={};
test[1]['b'] = 'test b';
var json = JSON.stringify(test);
alert(json);

Таким образом, вы преобразуете массив в JSON.

Да, массивы JavaScript не ассоциативные как массивы php, они предназначены для хранения данных с числовыми индексами. Но я понимаю, что вы хотите. Массивы JavaScript могут содержать как объектные свойства (например, “строковые индексы”), так и числа в строках (например, “числовые индексы”). Вы можете преобразовать/сериализовать их только в простой объект JavaScript {"key1": value1, "key2": value2, ...}.

Например, вы хотите преобразовать массив a, но избежать null/undefined в выводе (для ясности вопрос расширен):

a = []; // новый массив
a[0] = "UK";
a[1] = 7;
a["5"] = 8;
a["32"] = 77;
a[7] = 0;
a["8"] = NaN;
a[9] = undefined;
a["10"] = null;
a[11] = {};
a["19"] = false;
a[20] = true;
a['A'] = 'test';
a['B'] = 'test B';
a.push('wonderland');
a.push(98);
a[4294967294] = 42;
delete a[4294967294];
(4294967295) ['UK', 7, пусто × 3, 8, пусто, 0, NaN, undefined, null, {…}, пусто × 7, false, true, пусто × 11, 77, 'wonderland', 98, пусто × 4294967260, A: 'test', B: 'test B']
a.length
4294967295

Поэтому даже JSON.stringify(a) может быть возможным, это закончится многими null (и без ‘A’, ‘B’)…

'["UK",7,null,null,null,8,null,0,null,null,null,{},null,null,null,null,null,null,null,false,true,null,null,null,null,null,null,null,null,null,null,null,77,"wonderland",98,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,...

Мы можем это исправить с помощью Object.assign!

function replacer (key, value) {
  return (!!value || value === false || value === '' || typeof value === 'number') ? value : undefined;
}

JSON.stringify(Object.assign({}, a), replacer)

Или даже проще:

JSON.stringify(Object.assign({}, a))
'{"0":"UK","1":7,"5":8,"7":0,"8":null,"10":null,"11":{},"19":false,"20":true,"32":77,"33":"wonderland","34":98,"A":"test","B":"test B"}'

Обратное преобразование в массив сложнее:

jsonObj = JSON.stringify(Object.assign({}, a));
aObj = JSON.parse(jsonObj)

b = []
Object.entries(aObj).forEach(entry => b[entry[0]] = entry[1])
console.log(b)
(35) ['UK', 7, пусто × 3, 8, пусто, 0, null, пусто, null, {…}, пусто × 7, false, true, пусто × 11, 77, 'wonderland', 98, A: 'test', B: 'test B']

П. С. 1: Вы также можете видеть, что мы отфильтровали null/undefined для чрезвычайно длинного исходного массива (разреженный массив) a, что было невозможно с помощью array.filter.

П. С. 2: Но для нашей специфической задачи мы не можем использовать JSON.stringify(Object.assign([], a)), потому что не хотим упустить a['A'] = 'test'; a['B'] = 'test B'.

П. С. 3: Чтобы избежать использования таких неинтуитивных вещей, вы должны использовать простой объект {} или лучше Map.

JSON должен содержать пары ключ-значение. Однако вы все равно можете иметь массив в качестве части значения. Поэтому добавьте “ключ” на ваш выбор:

var json = JSON.stringify({whatever: test});

.

Ответ или решение

Почему JSON.stringify не работает с обычными массивами JavaScript?

Согласно вашему вопросу, вы столкнулись с проблемой при использовании функции JSON.stringify для сериализации массива, в котором используются строковые индексы вместо числовых. Давайте разберем суть проблемы.

Проблема с массивами JavaScript

В JavaScript массивы предназначены для хранения данных с числовыми индексами. Хотя вы можете добавить свойства с именованными ключами к массивам (поскольку массивы являются подтипом объектов), это не соответствует их основной концепции. Если вы попытаетесь сериализовать такую структуру с помощью JSON.stringify, функция не будет учитывать эти именованные свойства.

В приведенном вами примере кода:

var test = new Array();
test['a'] = 'test';
test['b'] = 'test b';
var json = JSON.stringify(test);
console.log(json);  // Вывод: ""

JSON.stringify возвращает пустую строку, поскольку в обычных массивах, содержимое которых было определено с именованными индексами, игнорируется.

Решения

Для корректного использования JSON.stringify, рекомендуется использовать объект вместо массива для хранения данных с именованными ключами. Вот как можно переписать ваш код:

const test = {}; // Используем объект
test.a = 'test';
test.b = 'test b';
var json = JSON.stringify(test);
console.log(json);  // Вывод: '{"a":"test","b":"test b"}'

Альтернативные подходы

Если вы все же хотите использовать массивы, можно рассмотреть несколько альтернативных подходов:

  1. Использование вложенных объектов:
    Вместо того чтобы пытаться использовать строковые индексы, вы можете использовать индексы массивов и заполнять их объектами:

    var test = [];
    test[0] = { key: 'a', value: 'test' };
    test[1] = { key: 'b', value: 'test b' };
    var json = JSON.stringify(test);
    console.log(json);  // Вывод: '[{"key":"a","value":"test"},{"key":"b","value":"test b"}]'
  2. Используйте функцию замены в JSON.stringify:
    Если вы хотите обеспечить специальную обработку для ваших массивов, вы можете использовать вторым аргументом для JSON.stringify функцию замены (replacer function):

    const arr = [];
    arr.a = 'test';
    arr.b = 'test b';
    
    var json = JSON.stringify({ arr }, function(key, value) {
       if (Array.isArray(value)) {
           return Object.assign({}, value); // Конвертирует массив в объект
       }
       return value;
    });
    console.log(json);  // Вывод: '{"arr":{"0":"test","1":"test b"}}'

Заключение

В заключение, при работе с JavaScript и JSON.stringify важно помнить, что массивы не предназначены для хранения именованных ключей. Если вам необходимо использовать именованные индексы, лучше всего воспользоваться объектами. Это обеспечит не только корректное поведение при сериализации, но и сделает ваш код более понятным и читаемым.

Не забывайте использовать правильные структуры данных в зависимости от задачи, и вы избежите многих распространенных ошибок.

Оцените материал
Добавить комментарий

Капча загружается...