Эта статья приняла участие"Проект "Звезда раскопок"", чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.
предисловие
«Добро пожаловать для обсуждения в области комментариев, официальный представитель NuggetsПроект «Звезда раскопок»После мероприятия в комментариях будет разыграно 100 штук Наггетсов.Подробнее о лотерее читайте в статье о мероприятии».
Разработчики должны благоговейно относиться к онлайн-среде: любая ошибка может привести к онлайн-сбоям, а также лишить вас годового бонуса (⊙︿⊙). такие как использование
JSON.stringify
, это очень знакомый, но очень незнакомый API.
Прочитав эту статью, вы сможете получить:
- Узнайте о грустной истории, которая чуть не стоила мне награды в конце года o(╥﹏╥)o
- Изучите 9 функций и правила преобразования JSON.stringify(акцент)
- Узнайте, как определить, имеет ли объект циклическую ссылку(акцент)
- Рукописный ввод JSON.stringify с нуля(акцент)
- так далее
рассказать грустную историю
Недавно из группы ушел маленький партнер, а часть бизнеса, за которую он отвечал, вел я, в итоге он просто взял на себя код, а код был еще горячим, и он чуть не попал в котел Р0. Пожалуйста, позвольте мне воспользоваться моментом, чтобы объяснить вам все входы и выходы.
начало печали
этот деньТолстая голова рыбыЯ бродил по морю кода и никак не мог выбраться, и меня внезапно затянуло в онлайн-группу по устранению неполадок, группа была довольно живой.
одноклассники по продуктуЖалобы: онлайн-пользователи не могут отправить форму, что приносит много жалоб клиентов.Предполагается, что это будет ошибка P0, и я надеюсь решить ее как можно скорее.
тестовые одноклассникиМне вот интересно: этот сценарий был опробован и предпроизводственная среда протестирована, почему он не может работать онлайн.
одноклассникиК слову о причине: в интерфейсе отсутствует поле со значением, что приводит к ошибке.
就是木有人说问题怎么解决!!!
就是木有人说问题怎么解决!!!
就是木有人说问题怎么解决!!!
Такая сцена не знает, если у вас есть? o (╥﹏╥) o.
проблема вызывает
Как показано ниже: Существует такая страница набора динамических форм после того, как пользователь выбирает или заполняет информацию (
各字段非必填情况下也可以直接提交
), затем передняя часть отправляет данные в заднюю часть и заканчивается, кажется, что у нее не так уж сложная логика.
Непосредственная причина ошибки
Если не требуется, поле signInfo будет
JSON.stringify
После того, как строковый объект отсутствуетvalue
key, что приводит к тому, что внутренний синтаксический анализ не может правильно прочитать значение, а затем сообщает об исключении в систему интерфейса, и пользователь не может перейти к следующему шагу.
// 异常入参数据,数组字符串中没有value key
{
signInfo: '[{"fieldId":539},{"fieldId":540},{"fieldId":546,"value":"10:30"}]'
}
// 正常入参数据
{
signInfo: '[{"fieldId":539,"value":"银卡"},{"fieldId":540,"value":"2021-03-01"},{"fieldId":546,"value":"10:30"}]'
}
Как генерируются аномальные данные
// 默认情况下数据是这样的
let signInfo = [
{
fieldId: 539,
value: undefined
},
{
fieldId: 540,
value: undefined
},
{
fieldId: 546,
value: undefined
},
]
// 经过JSON.stringify之后的数据,少了value key,导致后端无法读取value值进行报错
// 具体原因是`undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
console.log(JSON.stringify(signInfo))
// '[{"fieldId":539},{"fieldId":540},{"fieldId":546}]'
решение
Причина проблемы найдена и решение(Здесь упоминается только внешнее решение, конечно, оно также может быть решено бэкендом)Это также очень просто: преобразовать элемент, значение которого не определено, в пустую строку и отправить его.
Вариант 1. Откройте новый объект для обработки
let signInfo = [
{
fieldId: 539,
value: undefined
},
{
fieldId: 540,
value: undefined
},
{
fieldId: 546,
value: undefined
},
]
let newSignInfo = signInfo.map((it) => {
const value = typeof it.value === 'undefined' ? '' : it.value
return {
...it,
value
}
})
console.log(JSON.stringify(newSignInfo))
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'
Вариант 2: использоватьJSON.stringify
Второй параметр, непосредственно обрабатываемый
Недостаток схемы 1 в том, что для ее решения необходимо открыть новый объект и выполнить операцию по его решению.
不够优雅
let signInfo = [
{
fieldId: 539,
value: undefined
},
{
fieldId: 540,
value: undefined
},
{
fieldId: 546,
value: undefined
},
]
// 判断到value为undefined,返回空字符串即可
JSON.stringify(signInfo, (key, value) => typeof value === 'undefined' ? '' : value)
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'
история следовать
Изначально это страница, которая была в сети какое-то время.Почему эта проблема вдруг появилась, а раньше ее не было? После тщательных расспросов выяснилось, что одноклассники по продукту упоминали о небольшой точке оптимизации посередине.Ушедший мелкий партнер посчитал, что точка относительно небольшая, поэтому он изменил код и вышел в онлайн.Он никогда не думал, что будет онлайн проблемы.
Позже полный обзор этого дела от продукта к тесту, к бэкенду, к фронтенду делается отдельно, и подробности обсуждаться не будут.
Поскольку скорость от обнаружения проблемы до решения относительно высока, число затронутых пользователей невелико, а уровень ответственности еще не достигнут.Я сохранил свою годовую премию o(╥﹏╥)o.
Переучить json.stringify
После этого инцидента я чувствую необходимость пересмотреть
JSON.stringify
Этот метод, досконально изучите правила преобразования и попытайтесь реализовать его вручную.JSON.stringify
Если вы столкнулись с той же проблемой, что и я, приходите учиться снова, и у вас точно будет другой урожай!
Изучите JSON.stringify
JSON.stringify()
метод будетJavaScript 对象
или值
Преобразует в строку JSON, опционально заменяя значения, если указана функция заменителя, или опционально содержа только свойства, указанные массивом, если указанный заменитель является массивом.
Следующая информация взята изMDN
грамматика
JSON.stringify(value[, replacer [, space]])
-
value
Значение, которое будет сериализовано в строку JSON.
-
replacer
необязательный- Если параметр является функцией, в процессе сериализации каждый атрибут сериализованного значения будет преобразован и обработан функцией;
- Если параметр является массивом, только имена свойств, содержащиеся в этом массиве, будут сериализованы в окончательную строку JSON;
- Если этот параметр имеет значение null или не указан, все свойства объекта будут сериализованы.
-
space
необязательный- Укажите пустую строку для отступа, используемого для украшения вывода (красивая печать);
- Если аргумент является числом, он представляет количество пробелов; верхний предел равен 10.
- Если значение меньше 1, значит пробелов нет;
- Если параметр является строкой (когда длина строки превышает 10 букв, берутся первые 10 букв), строка будет восприниматься как пробел;
- Если этот параметр не указан (или имеет значение null), пробелов не будет.
возвращаемое значение
一个表示给定值的JSON字符串。
- Исключение возникает при создании циклической ссылки
TypeError
("циклическое значение объекта") - при попытке конвертировать
BigInt
Значение типа throwsTypeError
("значение BigInt не может быть сериализовано в JSON") (значение BigInt не может быть сериализовано в JSON).
основное использование
Уведомление
- JSON.stringify может преобразовывать объекты или значения (обычно больше используется для преобразования объектов)
- можно указать
replacer
дополнительная замена функций - можно также указать
replacer
представляет собой массив, который преобразует указанное свойство
вот толькоMDNпримерно наJSON.stringify
Одна из самых основных инструкций, давайте сначала попробуем эти функции.
// 1. 转换对象
console.log(JSON.stringify({ name: '前端胖头鱼', sex: 'boy' })) // '{"name":"前端胖头鱼","sex":"boy"}'
// 2. 转换普通值
console.log(JSON.stringify('前端胖头鱼')) // "前端胖头鱼"
console.log(JSON.stringify(1)) // "1"
console.log(JSON.stringify(true)) // "true"
console.log(JSON.stringify(null)) // "null"
// 3. 指定replacer函数
console.log(JSON.stringify({ name: '前端胖头鱼', sex: 'boy', age: 100 }, (key, value) => {
return typeof value === 'number' ? undefined : value
}))
// '{"name":"前端胖头鱼","sex":"boy"}'
// 4. 指定数组
console.log(JSON.stringify({ name: '前端胖头鱼', sex: 'boy', age: 100 }, [ 'name' ]))
// '{"name":"前端胖头鱼"}'
// 5. 指定space(美化输出)
console.log(JSON.stringify({ name: '前端胖头鱼', sex: 'boy', age: 100 }))
// '{"name":"前端胖头鱼","sex":"boy","age":100}'
console.log(JSON.stringify({ name: '前端胖头鱼', sex: 'boy', age: 100 }, null , 2))
/*
{
"name": "前端胖头鱼",
"sex": "boy",
"age": 100
}
*/
9 функций, о которых нужно помнить
Я просто использовал этот метод раньше, но я не разбирался в его правилах конвертации в деталях, их было целых 9.
Первая функция
-
undefined
,任意的函数
так же какsymbol值
,Появляться в非数组对象
игнорируется во время сериализации -
undefined
,任意的函数
так же какsymbol值
Появляться в数组
будет преобразован вnull
. -
undefined
,任意的函数
так же какsymbol值
одеяло单独转换
, возвращает неопределенное
// 1. 对象中存在这三种值会被忽略
console.log(JSON.stringify({
name: '前端胖头鱼',
sex: 'boy',
// 函数会被忽略
showName () {
console.log('前端胖头鱼')
},
// undefined会被忽略
age: undefined,
// Symbol会被忽略
symbolName: Symbol('前端胖头鱼')
}))
// '{"name":"前端胖头鱼","sex":"boy"}'
// 2. 数组中存在着三种值会被转化为null
console.log(JSON.stringify([
'前端胖头鱼',
'boy',
// 函数会被转化为null
function showName () {
console.log('前端胖头鱼')
},
//undefined会被转化为null
undefined,
//Symbol会被转化为null
Symbol('前端胖头鱼')
]))
// '["前端胖头鱼","boy",null,null,null]'
// 3.单独转换会返回undefined
console.log(JSON.stringify(
function showName () {
console.log('前端胖头鱼')
}
)) // undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol('前端胖头鱼'))) // undefined
Функция два
布尔值
,数字
,字符串
Объект обертки автоматически преобразуется в исходное значение, соответствующее сериализации.
console.log(JSON.stringify([new Number(1), new String("前端胖头鱼"), new Boolean(false)]))
// '[1,"前端胖头鱼",false]'
Функция три
все с
symbol
Атрибуты, являющиеся ключами атрибутов, полностью игнорируются, даже еслиreplacer
Обязательно включать их в параметр.
console.log(JSON.stringify({
[Symbol('前端胖头鱼')]: '前端胖头鱼'}
))
// '{}'
console.log(JSON.stringify({
[ Symbol('前端胖头鱼') ]: '前端胖头鱼',
}, (key, value) => {
if (typeof key === 'symbol') {
return value
}
}))
// undefined
Функция четыре
Числовые значения в форматах NaN и Infinity и null обрабатываются как null.
console.log(JSON.stringify({
age: NaN,
age2: Infinity,
name: null
}))
// '{"age":null,"age2":null,"name":null}'
Пятая функция
Преобразование значений Если есть метод toJSON(), этот метод определяет, какое значение будет сериализовано.
const toJSONObj = {
name: '前端胖头鱼',
toJSON () {
return 'JSON.stringify'
}
}
console.log(JSON.stringify(toJSONObj))
// "JSON.stringify"
Функция шесть
Date вызывает toJSON(), чтобы преобразовать его в строковую строку (такую же, как Date.toISOString()), поэтому она будет рассматриваться как строка.
const d = new Date()
console.log(d.toJSON()) // 2021-10-05T14:01:23.932Z
console.log(JSON.stringify(d)) // "2021-10-05T14:01:23.932Z"
Функция семь
Выполнение этого метода для объектов, содержащих циклические ссылки (объекты, ссылающиеся друг на друга, образующие бесконечный цикл), вызовет ошибку.
let cyclicObj = {
name: '前端胖头鱼',
}
cyclicObj.obj = cyclicObj
console.log(JSON.stringify(cyclicObj))
// Converting circular structure to JSON
Функция восемь
Другие типы объектов, включая Map/Set/WeakMap/WeakSet, будут сериализовать только перечисляемые свойства.
let enumerableObj = {}
Object.defineProperties(enumerableObj, {
name: {
value: '前端胖头鱼',
enumerable: true
},
sex: {
value: 'boy',
enumerable: false
},
})
console.log(JSON.stringify(enumerableObj))
// '{"name":"前端胖头鱼"}'
Особенность девять
при попытке конвертировать
BigInt
Значение типа выдает ошибку
const alsoHuge = BigInt(9007199254740991)
console.log(JSON.stringify(alsoHuge))
// TypeError: Do not know how to serialize a BigInt
рукописный JSON.stringify
наконец переучился
JSON.stringify
много функций! Давайте напишем простую версию, основанную на этих функциях (Нет функции замены и пространства)
реализация исходного кода
const jsonstringify = (data) => {
// 确认一个对象是否存在循环引用
const isCyclic = (obj) => {
// 使用Set数据类型来存储已经检测过的对象
let stackSet = new Set()
let detected = false
const detect = (obj) => {
// 不是对象类型的话,可以直接跳过
if (obj && typeof obj != 'object') {
return
}
// 当要检查的对象已经存在于stackSet中时,表示存在循环引用
if (stackSet.has(obj)) {
return detected = true
}
// 将当前obj存如stackSet
stackSet.add(obj)
for (let key in obj) {
// 对obj下的属性进行挨个检测
if (obj.hasOwnProperty(key)) {
detect(obj[key])
}
}
// 平级检测完成之后,将当前对象删除,防止误判
/*
例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
let tempObj = {
name: '前端胖头鱼'
}
let obj4 = {
obj1: tempObj,
obj2: tempObj
}
*/
stackSet.delete(obj)
}
detect(obj)
return detected
}
// 特性七:
// 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
if (isCyclic(data)) {
throw new TypeError('Converting circular structure to JSON')
}
// 特性九:
// 当尝试去转换 BigInt 类型的值会抛出错误
if (typeof data === 'bigint') {
throw new TypeError('Do not know how to serialize a BigInt')
}
const type = typeof data
const commonKeys1 = ['undefined', 'function', 'symbol']
const getType = (s) => {
return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
}
// 非对象
if (type !== 'object' || data === null) {
let result = data
// 特性四:
// NaN 和 Infinity 格式的数值及 null 都会被当做 null。
if ([NaN, Infinity, null].includes(data)) {
result = 'null'
// 特性一:
// `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined
} else if (commonKeys1.includes(type)) {
// 直接得到undefined,并不是一个字符串'undefined'
return undefined
} else if (type === 'string') {
result = '"' + data + '"'
}
return String(result)
} else if (type === 'object') {
// 特性五:
// 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
// 特性六:
// Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
if (typeof data.toJSON === 'function') {
return jsonstringify(data.toJSON())
} else if (Array.isArray(data)) {
let result = data.map((it) => {
// 特性一:
// `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null`
return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
})
return `[${result}]`.replace(/'/g, '"')
} else {
// 特性二:
// 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
if (['boolean', 'number'].includes(getType(data))) {
return String(data)
} else if (getType(data) === 'string') {
return '"' + data + '"'
} else {
let result = []
// 特性八
// 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
Object.keys(data).forEach((key) => {
// 特性三:
// 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
if (typeof key !== 'symbol') {
const value = data[key]
// 特性一
// `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
if (!commonKeys1.includes(typeof value)) {
result.push(`"${key}":${jsonstringify(value)}`)
}
}
})
return `{${result}}`.replace(/'/, '"')
}
}
}
}
тест один
// 1. 测试一下基本输出
console.log(jsonstringify(undefined)) // undefined
console.log(jsonstringify(() => { })) // undefined
console.log(jsonstringify(Symbol('前端胖头鱼'))) // undefined
console.log(jsonstringify((NaN))) // null
console.log(jsonstringify((Infinity))) // null
console.log(jsonstringify((null))) // null
console.log(jsonstringify({
name: '前端胖头鱼',
toJSON() {
return {
name: '前端胖头鱼2',
sex: 'boy'
}
}
}))
// {"name":"前端胖头鱼2","sex":"boy"}
// 2. 和原生的JSON.stringify转换进行比较
console.log(jsonstringify(null) === JSON.stringify(null));
// true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
// true
console.log(jsonstringify(false) === JSON.stringify(false));
// true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
// true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
// true
let str = "前端胖头鱼";
console.log(jsonstringify(str) === JSON.stringify(str));
// true
let reg = new RegExp("\w");
console.log(jsonstringify(reg) === JSON.stringify(reg));
// true
let date = new Date();
console.log(jsonstringify(date) === JSON.stringify(date));
// true
let sym = Symbol('前端胖头鱼');
console.log(jsonstringify(sym) === JSON.stringify(sym));
// true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
// true
let obj = {
name: '前端胖头鱼',
age: 18,
attr: ['coding', 123],
date: new Date(),
uni: Symbol(2),
sayHi: function () {
console.log("hello world")
},
info: {
age: 16,
intro: {
money: undefined,
job: null
}
},
pakingObj: {
boolean: new Boolean(false),
string: new String('前端胖头鱼'),
number: new Number(1),
}
}
console.log(jsonstringify(obj) === JSON.stringify(obj))
// true
console.log((jsonstringify(obj)))
// {"name":"前端胖头鱼","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖头鱼","number":1}}
console.log(JSON.stringify(obj))
// {"name":"前端胖头鱼","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖头鱼","number":1}}
// 3. 测试可遍历对象
let enumerableObj = {}
Object.defineProperties(enumerableObj, {
name: {
value: '前端胖头鱼',
enumerable: true
},
sex: {
value: 'boy',
enumerable: false
},
})
console.log(jsonstringify(enumerableObj))
// {"name":"前端胖头鱼"}
// 4. 测试循环引用和Bigint
let obj1 = { a: 'aa' }
let obj2 = { name: '前端胖头鱼', a: obj1, b: obj1 }
obj2.obj = obj2
console.log(jsonstringify(obj2))
// TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
// TypeError: Do not know how to serialize a BigInt
Из приведенного выше теста видно, чтоjsonstringify
основные иJSON.stringify
Производительность стабильна (также возможно, что тестовые примеры недостаточно полны, добро пожаловать на совместное обучение)
конец
Из-за бага заново учусь
JSON.stringify
, я узнал, что в нем все еще так много функций, которые я обычно не замечаю. Круг внешних развлечений слишком глубок. Я надеюсь, что со всеми будут обращаться мягко, с меньшим количеством ошибок и большим вниманием. Спокойной ночи