博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
当JSON.parse“遇上”非键值对
阅读量:7235 次
发布时间:2019-06-29

本文共 11456 字,大约阅读时间需要 38 分钟。

前言

在json大行其道并作为前后端主要通讯的数据格式之一时,对json本身的使用和了解多少人都会有些概念,当然随之而来的也是对json的对象以及其字符串形式的互相转换。在历史久远的过去,前端是通过低能的eval来实现格式转换的。

那么作为常识,我们知道JSON提供了两个常用的工具方法可以实现互相转换,分别是JSON.parse(),以及JSON.stringfy();常识的另外一方面,我们也知道一般情况下,我们处理的后端返回的对象都是标准的键值对格式,比如:{code:200,message:'success',data:{page:1,list:[]}}

那当后端或者其他场景下,我们将其他值类型转换的时候会发生什么呢?产生这个想法是因为在处理业务的时候发现,后端有个字段,其图片列表的字段值,返回的是‘[url1,url2]’,很显然其是数组字符串后的结果。开始我并没有想到用parse方法,因为脑中局限于这不是一个json数据。

什么是json数据

我们知道json是js对象表示法的子集,其标准的定义里有以下几条规则:

  • 数据在名称、值对中
  • 数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组

那么一些常见的数据类型,比如字符串,布尔型,null,undefined,数字,还有引用类型的函数、对象,数组这些属于json么?或者说其字符串化是否支持转化么?

我进行了一些案例验证,这里直接将结果公布出来,大家有兴趣的可以去校验下是不是这样的结果。

JSON.parse('true') //trueJSON.parse('false') //falseJSON.parse('str') //Uncaught SyntaxError: Unexpected token d in JSON at position 0JSON.parse('345str') //Uncaught SyntaxError: Unexpected token d in JSON at position 3 ,其报错的位置是出现字符串非数字的时候JSON.parse('345') //345JSON.parse('null') //nullJSON.parse("undefined") //Uncaught SyntaxError: Unexpected token d in JSON at position 0JSON.parse("[]") //[]JSON.parse('[1,"5"]')//[1,"5"]JSON.parse("{}")//{}JSON.parse('{1,5}')//Uncaught SyntaxError: Unexpected token d in JSON at position 1JSON.parse('{1:1}')//Uncaught SyntaxError: Unexpected token d in JSON at position 1JSON.parse('{"name":1}')//{name:1}复制代码

追根溯源

要想知道为什么是这样的结果,我们就要分析下其parse方法底层写了哪些逻辑了。

这里重点分析为什么支持这些非键值对的类型,而有些为什么又不支持。

首先我们要有个基本概念理解下:String在解析之前进行了一次字符串格式的整理,来保证整体字符是有效的,然后根据第一个字符进行了分类,不符合预期情况的都会报未期待的字符错误。然后其会特征性的区分的包括但不局限于以下的特殊情况。

字符 调用函数
{
ParseJsonObject
f 判断是否是false
t 判断是否是true
n 判断是否是null
含有数字,已0-9 或者 负数标识 - 开始的 查看整体是不是数字

源码追踪:整体逻辑

我们在源码角度找到了如下的代码:需要翻墙才可以看到。 其对应的源文件地址为 :

// 情况一 :发现了首字符是字符串标识的引号,用ParseJsonString实现 if (c0_ == '"') return ParseJsonString();// 情况二 :发现是0-9 数字开始,或者 - 开始的,有可能是数字类型,用转换为数字的方法进行转换 if ((c0_ >= '0' && c0_ <= '9') || c0_ == '-') return ParseJsonNumber();  // 情况三 :发现开始是对象左侧标记 { ,用json对象的解析方法  if (c0_ == '{') return ParseJsonObject();  // 情况四 :发现是 [ 开始的,尝试用数组转换的方法去转换  if (c0_ == '[') return ParseJsonArray();    // 情况五 :排除特殊的一些数据类型,比如true,false,null的字符串化  if (c0_ == 'f') {    if (AdvanceGetChar() == 'a' && AdvanceGetChar() == 'l' &&        AdvanceGetChar() == 's' && AdvanceGetChar() == 'e') {      AdvanceSkipWhitespace();      return factory()->false_value();    }    return ReportUnexpectedCharacter();  }  if (c0_ == 't') {    if (AdvanceGetChar() == 'r' && AdvanceGetChar() == 'u' &&        AdvanceGetChar() == 'e') {      AdvanceSkipWhitespace();      return factory()->true_value();    }    return ReportUnexpectedCharacter();  }  if (c0_ == 'n') {    if (AdvanceGetChar() == 'u' && AdvanceGetChar() == 'l' &&        AdvanceGetChar() == 'l') {      AdvanceSkipWhitespace();      return factory()->null_value();    }    return ReportUnexpectedCharacter();  }复制代码

源码追踪:具体方法

ParseJsonString

因为这部分代码的语言自己没有去学习过,就简单分析下里面处理的一些逻辑吧,主要处理了内容是否是单字节,以及一些特殊符号的处理。

template 
bool JsonParser
::ParseJsonString(Handle
expected) { int length = expected->length(); if (source_->length() - position_ - 1 > length) { DisallowHeapAllocation no_gc; String::FlatContent content = expected->GetFlatContent(); if (content.IsOneByte()) { DCHECK_EQ('"', c0_); const uint8_t* input_chars = seq_source_->GetChars() + position_ + 1; const uint8_t* expected_chars = content.ToOneByteVector().start(); for (int i = 0; i < length; i++) { uint8_t c0 = input_chars[i]; if (c0 != expected_chars[i] || c0 == '"' || c0 < 0x20 || c0 == '\\') { return false; } } if (input_chars[length] == '"') { position_ = position_ + length + 1; AdvanceSkipWhitespace(); return true; } } } return false;}ParseJsonString复制代码

ParseJsonArray

核心是处理了当其结尾是否是]区分,数组处理的前提是右边的结束符必须是] . 如果不是,那么就会按照ParseJsonValue进行转换,当发现转换为对象失败,比如说发现是null,或者一些特殊情况的时候,就会报错不可预期的字符串错误; 如果右侧是],则可能是数组,按照简单数组以及复杂数组分别处理,简单数组就会指定固定一个数组然后返回这个数组。

// Parse a JSON array. Position must be right at '['.template 
Handle
JsonParser
::ParseJsonArray() { HandleScope scope(isolate()); ZoneList
> elements(4, zone()); DCHECK_EQ(c0_, '['); ElementKindLattice lattice; AdvanceSkipWhitespace(); if (c0_ != ']') { do { Handle element = ParseJsonValue(); if (element.is_null()) return ReportUnexpectedCharacter(); elements.Add(element, zone()); lattice.Update(element); } while (MatchSkipWhiteSpace(',')); if (c0_ != ']') { return ReportUnexpectedCharacter(); } } AdvanceSkipWhitespace(); // Allocate a fixed array with all the elements. Handle json_array; const ElementsKind kind = lattice.GetElementsKind(); switch (kind) { case PACKED_ELEMENTS: case PACKED_SMI_ELEMENTS: { Handle
elems = factory()->NewFixedArray(elements.length(), pretenure_); for (int i = 0; i < elements.length(); i++) elems->set(i, *elements[i]); json_array = factory()->NewJSArrayWithElements(elems, kind, pretenure_); break; } case PACKED_DOUBLE_ELEMENTS: { Handle
elems = Handle
::cast( factory()->NewFixedDoubleArray(elements.length(), pretenure_)); for (int i = 0; i < elements.length(); i++) { elems->set(i, elements[i]->Number()); } json_array = factory()->NewJSArrayWithElements(elems, kind, pretenure_); break; } default: UNREACHABLE(); } return scope.CloseAndEscape(json_array);}ParseJsonArray复制代码

ParseJsonNumber

核心判断了一些负数,0,1-9,小数点等不同情况的处理,并对不符合情况的抛出异常字符。

template 
Handle
JsonParser
::ParseJsonNumber() { bool negative = false; int beg_pos = position_; if (c0_ == '-') { Advance(); negative = true; } if (c0_ == '0') { Advance(); // Prefix zero is only allowed if it's the only digit before // a decimal point or exponent. if (IsDecimalDigit(c0_)) return ReportUnexpectedCharacter(); } else { int i = 0; int digits = 0; if (c0_ < '1' || c0_ > '9') return ReportUnexpectedCharacter(); do { i = i * 10 + c0_ - '0'; digits++; Advance(); } while (IsDecimalDigit(c0_)); if (c0_ != '.' && c0_ != 'e' && c0_ != 'E' && digits < 10) { SkipWhitespace(); return Handle
(Smi::FromInt((negative ? -i : i)), isolate()); } } if (c0_ == '.') { Advance(); if (!IsDecimalDigit(c0_)) return ReportUnexpectedCharacter(); do { Advance(); } while (IsDecimalDigit(c0_)); } if (AsciiAlphaToLower(c0_) == 'e') { Advance(); if (c0_ == '-' || c0_ == '+') Advance(); if (!IsDecimalDigit(c0_)) return ReportUnexpectedCharacter(); do { Advance(); } while (IsDecimalDigit(c0_)); } int length = position_ - beg_pos; double number; if (seq_one_byte) { Vector
chars(seq_source_->GetChars() + beg_pos, length); number = StringToDouble(isolate()->unicode_cache(), chars, NO_FLAGS, // Hex, octal or trailing junk. std::numeric_limits
::quiet_NaN()); } else { Vector
buffer = Vector
::New(length); String::WriteToFlat(*source_, buffer.start(), beg_pos, position_); Vector
result = Vector
(buffer.start(), length); number = StringToDouble(isolate()->unicode_cache(), result, NO_FLAGS, // Hex, octal or trailing junk. 0.0); buffer.Dispose(); } SkipWhitespace(); return factory()->NewNumber(number, pretenure_);}ParseJsonNumber复制代码

ParseJsonObject

核心判断了末尾是不是}来保证json对象,以及严格校验是否复核键值对的基本格式。

// Parse a JSON object. Position must be right at '{'.template 
Handle
JsonParser
::ParseJsonObject() { HandleScope scope(isolate()); Handle
json_object = factory()->NewJSObject(object_constructor(), pretenure_); Handle
map(json_object->map()); int descriptor = 0; ZoneList
> properties(8, zone()); DCHECK_EQ(c0_, '{'); bool transitioning = true; AdvanceSkipWhitespace(); if (c0_ != '}') { do { if (c0_ != '"') return ReportUnexpectedCharacter(); int start_position = position_; Advance(); if (IsDecimalDigit(c0_)) { ParseElementResult element_result = ParseElement(json_object); if (element_result == kNullHandle) return Handle::null(); if (element_result == kElementFound) continue; } // Not an index, fallback to the slow path. position_ = start_position;#ifdef DEBUG c0_ = '"';#endif Handle
key; Handle
value; // Try to follow existing transitions as long as possible. Once we stop // transitioning, no transition can be found anymore. DCHECK(transitioning); // First check whether there is a single expected transition. If so, try // to parse it first. bool follow_expected = false; Handle target; if (seq_one_byte) { key = TransitionArray::ExpectedTransitionKey(map); follow_expected = !key.is_null() && ParseJsonString(key); } // If the expected transition hits, follow it. if (follow_expected) { target = TransitionArray::ExpectedTransitionTarget(map); } else { // If the expected transition failed, parse an internalized string and // try to find a matching transition. key = ParseJsonInternalizedString(); if (key.is_null()) return ReportUnexpectedCharacter(); target = TransitionArray::FindTransitionToField(map, key); // If a transition was found, follow it and continue. transitioning = !target.is_null(); } if (c0_ != ':') return ReportUnexpectedCharacter(); AdvanceSkipWhitespace(); value = ParseJsonValue(); if (value.is_null()) return ReportUnexpectedCharacter(); if (transitioning) { PropertyDetails details = target->instance_descriptors()->GetDetails(descriptor); Representation expected_representation = details.representation(); if (value->FitsRepresentation(expected_representation)) { if (expected_representation.IsHeapObject() && !target->instance_descriptors() ->GetFieldType(descriptor) ->NowContains(value)) { Handle
value_type( value->OptimalType(isolate(), expected_representation)); Map::GeneralizeField(target, descriptor, details.constness(), expected_representation, value_type); } DCHECK(target->instance_descriptors() ->GetFieldType(descriptor) ->NowContains(value)); properties.Add(value, zone()); map = target; descriptor++; continue; } else { transitioning = false; } } DCHECK(!transitioning); // Commit the intermediate state to the object and stop transitioning. CommitStateToJsonObject(json_object, map, &properties); JSObject::DefinePropertyOrElementIgnoreAttributes(json_object, key, value) .Check(); } while (transitioning && MatchSkipWhiteSpace(',')); // If we transitioned until the very end, transition the map now. if (transitioning) { CommitStateToJsonObject(json_object, map, &properties); } else { while (MatchSkipWhiteSpace(',')) { HandleScope local_scope(isolate()); if (c0_ != '"') return ReportUnexpectedCharacter(); int start_position = position_; Advance(); if (IsDecimalDigit(c0_)) { ParseElementResult element_result = ParseElement(json_object); if (element_result == kNullHandle) return Handle
::null(); if (element_result == kElementFound) continue; } // Not an index, fallback to the slow path. position_ = start_position;#ifdef DEBUG c0_ = '"';#endif Handle
key; Handle
value; key = ParseJsonInternalizedString(); if (key.is_null() || c0_ != ':') return ReportUnexpectedCharacter(); AdvanceSkipWhitespace(); value = ParseJsonValue(); if (value.is_null()) return ReportUnexpectedCharacter(); JSObject::DefinePropertyOrElementIgnoreAttributes(json_object, key, value) .Check(); } } if (c0_ != '}') { return ReportUnexpectedCharacter(); } } AdvanceSkipWhitespace(); return scope.CloseAndEscape(json_object);}ParseJsonObject复制代码

我的方法重写

假设如果浏览器底层没有支持这些方法,我们该如何底层用js封装一个函数呢?可以参考下我的一个案例。(仅供参考学习)

参考文档

转载地址:http://ivlfm.baihongyu.com/

你可能感兴趣的文章
将Tomcat设置为自动启动的服务最快捷方法
查看>>
Elasticsearch安装(四), elasticsearch head 插件安装和使用。
查看>>
UGUI之Canvas和EventSystem
查看>>
atitit.web 推送实现方案集合
查看>>
java中byte, iso-8859-1, UTF-8,乱码的根源
查看>>
git web 服务器的搭建【转】
查看>>
初窥ElasticSearch
查看>>
identity_insert---实验性插入大批量数据和分页存储过程
查看>>
键盘游戏之canvas--用OO方式写
查看>>
Leetcode: Scramble String
查看>>
JavaWeb--中文乱码小结
查看>>
MySQL优化经验和方法汇总
查看>>
JAVA获取CLASSPATH路径--转
查看>>
Linux 下测试网卡性能命令iperf 的用法
查看>>
Qt编写自定义控件大全
查看>>
.yml是什么文件
查看>>
工作总结 datatable 里的 数据 rows Columns
查看>>
006-生命周期和插件
查看>>
Geometry Imager Viewport Filter
查看>>
mysql 批量kill locked 进程
查看>>