layout: post title: 【短篇】小心字符串中的转义字符 modified: 2016-12-27 tags: [javascript, short] image: feature: abstract-3.jpg credit: dargadgetz creditlink: http://www.dargadgetz.com/ios-7-abstract-wallpaper-pack-for-iphone-5-and-ipod-touch-retina/ comments: true
这是一篇遇见bug的反思。
首先请听我描述一段业务上的需求。
有一些信息是需要客户填写,比如说录入完商品之后需要填写商品的描述信息。这些信息存储在后台,在需要时由前端请求。但出于某种原因后端只能返回字符串,而前端需要JSON,于是妥协的结果后端返回的是stringify之后的json对象的字符串。
比如我们需要的是:
{% highlight javascript %} { "name": "banana" } {% endhighlight %}
而后端返回的是
{% highlight javascript %} '{"name": "banana"}' {% endhighlight %}
然后我们再用JSON.parse
方法将字符串还原为JSON对象用于使用。
一切都很完美,直到最近遇到的这个问题出现:客户可能在商品的描述中加入英文双引号"
。于是想当然的,我们会在存储该信息的时候对双引号进行转义。例如当用户输入的是"Hello"
时,我们在存储时会存储为\"Hello\"
。
问题来了:假设后端存储的信息是{"name": "\"banana\""}
,那么它返回的字符串也是{"name": "\"banana\""}
,但是对于javascript来说返回(console打印的结果也是)的字符串却是{"name": ""banana""}
,于是调用JSON.parse
方法时报错:
{% highlight javascript %} JSON.parse('{"name": ""banana""}') {% endhighlight %}
而之所以报错是因为双引号的缘故。但我知道你其实想问的是,用于转义的反斜杠\
哪里去了?
不妨让我们从头捋一捋,为什么需要转义。
转义的原因无非有两种(改编自百度百科)
{% highlight javascript %} var str = "Hello" {% endhighlight %}
{% highlight javascript %} var str = "Hello"" {% endhighlight %}
{% highlight javascript %} var str = "Hello\"" {% endhighlight %}
请注意加上反斜杠进行转义,只是为了在书写代码时有所区分,骨子里转义双引号\"
仍然还是双引号"
。例如你可以运行下面这段代码:
{% highlight javascript %} var str = "\"\"\""; console.log(str); // """ {% endhighlight %}
上面代码中在打印时,反斜杠已经不存在了,反斜杠存在的意义是为了保证双引号不被误解。
使用单引号也能达到同样的目的,此时也就不需要进行转义了:
{% highlight javascript %} var str = '"""'; console.log(str); // """ {% endhighlight %}
这其实能得出一个吊诡的结论:一个对象原封不动的转化为字符串后,这个字符串竟然不能还原为对象:
{% highlight javascript %} // 原始object对象 var obj = { key: '\"Hello World\"' }
var str = JSON.stringify(obj); // 转化为字符串 console.log(str) // {"key":"\"Hello World\""}
var o = JSON.parse(str) // 还原为object时出错 {% endhighlight %}
回到开始的那个问题。我们现在知道为什么当后端返回给我们{"name": "\"banana\""}
时,我们实际上得到的是{"name": ""banana""}
了。对脚本引擎来说,\"
与"
是一样的,这样的字符串当然不能被JSON.parse
。
那么什么样的字符串才能被JSON.parse
解析?后端返回字符串经过脚本引擎处理后仍然保留反斜杠的字符串:{"name": "\"banana\""}
反过来推算,如果我们想字符串能被JSON.parse
,则需要\"
中的反斜杠得以保留,也就是最终的结果应该为JSON.parse(
{"name": "\"banana\""})
,如果想反斜杠得以保留,则需要反斜杠不被用于转义,则需要在反斜杠之前再加反斜杠将其转义以防止它被用于转义(是不是有点绕,好好理解一下)。最后得出结论,后端存储时,应该存储的内容是:
{% highlight javascript %} {"name": "\"banana\""} {% endhighlight %}
我的忠告是,在处理类似场景时千万小心,仔细判断是否需要对反斜杠进行转义。当然上上策还是直接使用JSONP,而不是把对象压缩为字符串。