javascript 递归 JSON.stringify 实现
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22333101/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
recursive JSON.stringify implementation
提问by cilop
I am trying to learn recursion in Javascript, so I figured I'd rewrite the native JSON.stringify
function using recursion as a challenge to myself. I almost got my code to work:
我正在尝试在 Javascript 中学习递归,所以我想我会JSON.stringify
使用递归重写本机函数作为对自己的挑战。我几乎让我的代码工作:
var my_stringify = function(obj){
value = obj[ Object.keys(obj)[0] ];
index = Object.keys(obj)[0];
delete obj[ Object.keys(obj)[0] ];
// The value is just a simple string, not a nested object
if (typeof value === 'string'){
if (Object.keys(obj).length !== 0){
// Continue recursion ..
return '"' + index + '":"' + value + '",' + my_stringify(obj);
}
// This would be the base case with a string at the end. Stop recursion.
return '"' + index + '":"' + value + '"}';
}
// The value is actually a nested object
else{
if (Object.keys(obj).length !== 0){
// Continue recursion ..
return '"' + index + '":{' + my_stringify(value) + ',' + my_stringify(obj);
}
// This is the base case with a nested object at the end. Stringify it and end recursion.
return '"' + index + '":{' + my_stringify(value) + '}';
}
}
Except for the fact that the first {
in my answer is missing, and I can't figure out how to fix this bug.
除了{
我的答案中的第一个丢失这一事实之外,我无法弄清楚如何修复此错误。
E.g. my_stringify({foo: 'bar'})
returns "foo":"bar"}
instead of {"foo":"bar"}
.
例如my_stringify({foo: 'bar'})
返回"foo":"bar"}
而不是{"foo":"bar"}
.
Also, I'm aware I'm completely destroying the original object, is there any way to send over to recursion a reduced version of the original object without deleting anything (something like obj.slice(1)
)?
另外,我知道我正在完全破坏原始对象,有没有办法在不删除任何内容(例如obj.slice(1)
)的情况下将原始对象的简化版本发送到递归?
Any advice will be greatly appreciated !
任何建议将不胜感激!
回答by Thank you
New answer to an old question
旧问题的新答案
There's some painfully bad answers here that fail under even the simplest examples. This answer aims to answer the question exhaustively and demonstrate how an approach like this scales even when handling a wide variety of data types and ...
这里有一些非常糟糕的答案,即使在最简单的例子下也失败了。这个答案旨在详尽地回答这个问题,并展示即使在处理各种数据类型和......
Corner cases
角箱
This function does a simple case analysis on a non-null data's constructor
property and encodes accordingly. It manages to cover a lot of corner cases that you're unlikely to consider, such as
此函数对非空数据的constructor
属性进行简单的案例分析并进行相应的编码。它设法涵盖了许多您不太可能考虑的极端情况,例如
JSON.stringify(undefined)
returnsundefined
JSON.stringify(null)
returns'null'
JSON.stringify(true)
returns'true'
JSON.stringify([1,2,undefined,4])
returns'[1,2,null,4]'
JSON.stringify({a: undefined, b: 2})
returns'{ "b": 2 }'
JSON.stringify({[undefined]: 1})
returns'{ "undefined": 1 }'
JSON.stringify({a: /foo/})
returns{ "a": {} }
JSON.stringify(undefined)
回报undefined
JSON.stringify(null)
回报'null'
JSON.stringify(true)
回报'true'
JSON.stringify([1,2,undefined,4])
回报'[1,2,null,4]'
JSON.stringify({a: undefined, b: 2})
回报'{ "b": 2 }'
JSON.stringify({[undefined]: 1})
回报'{ "undefined": 1 }'
JSON.stringify({a: /foo/})
回报{ "a": {} }
So to verify that our stringifyJSON
function actually works properly, I'm not going to test the output of it directly. Instead, I'm going to write a little test
method that ensures the JSON.parse
of our encoded JSON actually returns our original input value
因此,为了验证我们的stringifyJSON
函数实际上是否正常工作,我不打算直接测试它的输出。相反,我将编写一个小test
方法来确保JSON.parse
我们编码的 JSON 实际返回我们的原始输入值
// we really only care that JSON.parse can work with our result
// the output value should match the input value
// if it doesn't, we did something wrong in our stringifier
const test = data => {
return console.log(JSON.parse(stringifyJSON(data)))
}
test([1,2,3]) // should return [1,2,3]
test({a:[1,2,3]}) // should return {a:[1,2,3]}
Disclaimer:it should be obvious that the code I'm about to share is not meant to be used as an actual replacementfor
JSON.stringify
– there's countless corner cases we probably didn't address. Instead, this code is shared to provide a demonstration for how we could go about such a task. Additional corner cases could easily be added to this function.
免责声明:应该是显而易见的,我正要份额的代码并不意味着作为一个实际的替代的
JSON.stringify
-有我们可能没有涉及无数角落的情况。相反,共享此代码是为了演示我们如何执行此类任务。可以很容易地向这个功能添加额外的角落案例。
Runnable demo
可运行的演示
Without further ado, here is stringifyJSON
in a runnable demo that verifies excellent compatibility for several common cases
事不宜迟,这里是stringifyJSON
一个可运行的演示,它验证了几种常见情况的出色兼容性
const stringifyJSON = data => {
if (data === undefined)
return undefined
else if (data === null)
return 'null'
else if (data.constructor === String)
return '"' + data.replace(/"/g, '\"') + '"'
else if (data.constructor === Number)
return String(data)
else if (data.constructor === Boolean)
return data ? 'true' : 'false'
else if (data.constructor === Array)
return '[ ' + data.reduce((acc, v) => {
if (v === undefined)
return [...acc, 'null']
else
return [...acc, stringifyJSON(v)]
}, []).join(', ') + ' ]'
else if (data.constructor === Object)
return '{ ' + Object.keys(data).reduce((acc, k) => {
if (data[k] === undefined)
return acc
else
return [...acc, stringifyJSON(k) + ':' + stringifyJSON(data[k])]
}, []).join(', ') + ' }'
else
return '{}'
}
// round-trip test and log to console
const test = data => {
return console.log(JSON.parse(stringifyJSON(data)))
}
test(null) // null
test('he said "hello"') // 'he said "hello"'
test(5) // 5
test([1,2,true,false]) // [ 1, 2, true, false ]
test({a:1, b:2}) // { a: 1, b: 2 }
test([{a:1},{b:2},{c:3}]) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
test({a:[1,2,3], c:[4,5,6]}) // { a: [ 1, 2, 3 ], c: [ 4, 5, 6 ] }
test({a:undefined, b:2}) // { b: 2 }
test({[undefined]: 1}) // { undefined: 1 }
test([[["test","mike",4,["jake"]],3,4]]) // [ [ [ 'test', 'mike', 4, [ 'jake' ] ], 3, 4 ] ]
回答by Smeegs
You need to view recursion as going deeper into the object without actually altering the object. It looks like you're trying to use recursion to go sideways inside of an object.
您需要将递归视为深入对象而不实际更改对象。看起来您正在尝试使用递归在对象内部横向移动。
I've written a version of stringify that handles basic object (no arrays or functions).
我编写了一个处理基本对象(没有数组或函数)的 stringify 版本。
Here is the fiddle
这是小提琴
Here is the code:
这是代码:
var my_stringify2 = function (obj) {
var objKeys = Object.keys(obj);
var keyValueArray = new Array();
for (var i = 0; i < objKeys.length; i++) {
var keyValueString = '"' + objKeys[i] + '":';
var objValue = obj[objKeys[i]];
keyValueString = (typeof objValue == "string") ?
keyValueString = keyValueString + '"' + objValue + '"' :
keyValueString = keyValueString + my_stringify2(objValue);
keyValueArray.push(keyValueString);
}
return "{" + keyValueArray.join(",") + "}";
}
You want the recursion to do most of the work for you, and you should only need to handle basic conditions (which you already had). In my function the two acceptable conditions are string and object.
您希望递归为您完成大部分工作,您应该只需要处理基本条件(您已经拥有)。在我的函数中,两个可接受的条件是字符串和对象。
A string is handled on the spot, and an object is passed into the function recursively.
当场处理一个字符串,并递归地将一个对象传递给函数。
That's the key. You were passing the same object into the function repeatedly, removing the handled elements until you get to a point where the object is completely gone.
这就是关键。您反复将相同的对象传递给函数,删除处理过的元素,直到到达对象完全消失的程度。
What I did instead was pass the value of that particular property if it were an object. If it's a string, just add it to the string and move along.
如果它是一个对象,我所做的是传递该特定属性的值。如果它是一个字符串,只需将其添加到字符串中并继续。
Take a look at the code and let me know if you have any questions. Notice that the object that I'm passing in has a nested object.
看看代码,如果您有任何问题,请告诉我。请注意,我传入的对象有一个嵌套对象。
my_stringify2({
foo: 'bar',
bar: 'foo',
foobar: {
foo: 'bar',
bar: 'foo'
}
});
and the result is proper json
结果是正确的json
{"foo":"bar","bar":"foo","foobar":{"foo":"bar","bar":"foo"}}
If you're looking to completely avoid a for loop, you can do the following
如果您想完全避免 for 循环,您可以执行以下操作
in this one you pass the object like normal, but recursively you pass a key array, removing an element from the key array for each property.
在这个中,您像往常一样传递对象,但递归地传递一个键数组,从每个属性的键数组中删除一个元素。
a bit more complicated, so I added comments
有点复杂,所以我添加了评论
var my_stringify2 = function (obj, objKeys) {
var str = "";
// keys haven't been loaded, either first pass, or processing a value of type object
if (objKeys == undefined) {
objKeys = Object.keys(obj);
str = "{"
} else {
// if keys array exists and is empty, no more properties to evaluate, return the end bracket
if (objKeys.length == 0) {
return "}";
// array exists and isn't empty, that means it's a property and not the first property, add a comma
} else {
str = ",";
}
}
// add the property name
str += '"' + objKeys[0] + '":';
// get the value
var objValue = obj[objKeys[0]];
// if the value type is string, add the string, if it's an object, call this function again, but leave the objKeys undefined
str +=
(typeof objValue == "string") ?
'"' + objValue + '"' :
my_stringify2(objValue);
// remove the first element fromt the keys array
objKeys.splice(0,1);
//call the function for the next property
return str + my_stringify2(obj, objKeys);
}
回答by Cristian Torres
This has been answered several times but here is yet another solution:
这已被多次回答,但这是另一种解决方案:
Using es6:
使用es6:
let oldStringify = JSON.stringify;
JSON.stringify = (obj, replacer, space) => oldStringify(obj, replacer || ((key, value) => {if(key && value === obj) return "[recursive]"; return value;}), space)
回答by Rohit
I was asked this in an Interview question and this is what I came up with. An understandable recursive approach:
我在面试问题中被问到这个问题,这就是我想出的。一种可以理解的递归方法:
?
function stringify(input) {
var arrVals = [];
Object.keys(input).forEach(function(keyName) {
let val = input[keyName];
if (typeof val !== 'undefined' && typeof val !== 'function') {
arrVals.push(getQuotedString(keyName) + ":" + getString(val));
}
});
return '{' + arrVals.join(',') + '}';
}
function getString(val) {
switch (typeof val) {
case 'string':
return getQuotedString(val);
break;
case 'number':
case 'boolean':
return val;
break;
case 'object':
if (val === null) {
return "null";
}
if (Array.isArray(val)) {
let arrString = []
for (let i = 0; i < val.length; i++) {
arrString.push(getString(val[i]));
}
return "[" + arrString.join(',') + "]";
}
return stringify(val);
break;
}
}
function getQuotedString(str) {
return '"' + str + '"';
}
Test using following obj:
使用以下 obj 进行测试:
?
var input = {
"a": 1,
"b": 'text',
"c": {
"x": 1,
"y": {
"x": 2
}
},
"d": false,
"e": null,
"f": undefined,
"g": [1, "text", {
a: 1,
b: 2
}, null]
};
回答by Matt Burland
Fundamentally, you are stringifying by cutting off the first property, stringifying that and then recursing of the rest of the object. IMHO this is not the way to go, the only reason to recurse is when there is a nested object, otherwise you should just iterate through the properties. As you've done it, you've made it much more difficult to tell if you are at the beginning of the object and should return that missing {
with your string.
从根本上说,您通过切断第一个属性,将其字符串化,然后递归对象的其余部分来进行字符串化。恕我直言,这不是要走的路,递归的唯一原因是存在嵌套对象时,否则您应该遍历属性。当你这样做时,你已经很难判断你是否在对象的开头,并且应该{
用你的字符串返回缺失的部分。
In semi-pseudo code (leaving you some work to do yourself), you want something like this:
在半伪代码中(让你自己做一些工作),你想要这样的东西:
var my_stringify = function(obj) {
// check first for null / undefined / etc and return
var myJSON = "{";
// iterate through all the properties of the object
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
// check to see if this property is a string, number, etc
if (//...) {
myJSON += // the JSON representation of this value using p and obj[p]
}
if (// test for nested object) {
myJSON += my_stringify(obj[p]); // this is recursion!
}
if (// test for arrays) {
// arrays also need special handling and note that they might
// include objects or other arrays - more chances for recursion!
}
// note: functions should be ignored, they aren't included in JSON
}
}
return myJSON + "}";
}
回答by Jordan Running
I disagree with @Bergi's assertion that regular old recursion isn't a good fit for this. Like I said in my comment you can avoid the use of a for
loop by passing the index as an argument to the function. This is a very common technique and prevents you from needing to copy or modify the data structure.
我不同意@Bergi 的断言,即常规的旧递归不适合于此。就像我在评论中所说的那样,您可以for
通过将索引作为参数传递给函数来避免使用循环。这是一种非常常见的技术,可以防止您需要复制或修改数据结构。
Here's my stab at such an implementation. As you can see it's really straightforward (and to my own surprise, it works!):
这是我对这样一个实现的尝试。如您所见,它非常简单(令我惊讶的是,它确实有效!):
function jsonify(obj, idx) {
var json, objStr = toString.call(obj);
// Handle strings
if(objStr == '[object String]') { return '"' + obj + '"' }
idx = idx || 0
// Handle arrays
if(objStr == '[object Array]') {
if(idx >= obj.length) {
// The code below ensures we'll never go past the end of the array,
// so we can assume this is an empty array
return "[]"
}
// JSONify the value at idx
json = jsonify( obj[idx] )
if(idx < obj.length - 1) {
// There are items left in the array, so increment the index and
// JSONify the rest
json = json + "," + jsonify( obj, idx + 1 )
}
// If this is the first item in the array, wrap the result in brackets
if(idx === 0) { return "[" + json + "]" }
return json
}
// Handle objects
if(obj === Object(obj)) {
var keys = Object.keys(obj)
var key = keys[idx]
// JSONify the key and value
json = '"' + key + '":' + jsonify( obj[key] )
if(idx < keys.length - 1) {
// There are more keys, so increment the index and JSONify the rest
return json + "," + jsonify( obj, idx + 1 )
}
// If this is the first key, wrap the result in curly braces
if(idx === 0) { return "{" + json + "}" }
return json
}
return obj.toString() // Naively handle everything else
}
var items = [ 9, "nine", { "key": [], "key2": { "subkey": 3.333 } } ]
console.log("OUTPUT", jsonify(items))
// => OUTPUT [9,"nine","key":[],"key2":{"subkey":3.333}]
There are a number of ways this could be tightened up (and I'm sure there are some bugs, too), but you get the idea.
有很多方法可以加强这一点(我相信也有一些错误),但你明白了。