前言
在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') //true
JSON.parse('false') //false
JSON.parse('str') //Uncaught SyntaxError: Unexpected token d in JSON at position 0
JSON.parse('345str') //Uncaught SyntaxError: Unexpected token d in JSON at position 3 ,其报错的位置是出现字符串非数字的时候
JSON.parse('345') //345
JSON.parse('null') //null
JSON.parse("undefined") //Uncaught SyntaxError: Unexpected token d in JSON at position 0
JSON.parse("[]") //[]
JSON.parse("[1,'5']")//[1,'5']
JSON.parse("{}")//{}
JSON.parse('{1,5}')//Uncaught SyntaxError: Unexpected token d in JSON at position 1
JSON.parse('{1:1}')//Uncaught SyntaxError: Unexpected token d in JSON at position 1
JSON.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 seq_one_byte>
bool JsonParser<seq_one_byte>::ParseJsonString(Handle<String> 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 <bool seq_one_byte>
Handle<Object> JsonParser<seq_one_byte>::ParseJsonArray() {
HandleScope scope(isolate());
ZoneList<Handle<Object> > elements(4, zone());
DCHECK_EQ(c0_, '[');
ElementKindLattice lattice;
AdvanceSkipWhitespace();
if (c0_ != ']') {
do {
Handle<Object> 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<Object> json_array;
const ElementsKind kind = lattice.GetElementsKind();
switch (kind) {
case PACKED_ELEMENTS:
case PACKED_SMI_ELEMENTS: {
Handle<FixedArray> 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<FixedDoubleArray> elems = Handle<FixedDoubleArray>::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 <bool seq_one_byte>
Handle<Object> JsonParser<seq_one_byte>::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>(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<const uint8_t> chars(seq_source_->GetChars() + beg_pos, length);
number = StringToDouble(isolate()->unicode_cache(), chars,
NO_FLAGS, // Hex, octal or trailing junk.
std::numeric_limits<double>::quiet_NaN());
} else {
Vector<uint8_t> buffer = Vector<uint8_t>::New(length);
String::WriteToFlat(*source_, buffer.start(), beg_pos, position_);
Vector<const uint8_t> result =
Vector<const uint8_t>(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 <bool seq_one_byte>
Handle<Object> JsonParser<seq_one_byte>::ParseJsonObject() {
HandleScope scope(isolate());
Handle<JSObject> json_object =
factory()->NewJSObject(object_constructor(), pretenure_);
Handle<Map> map(json_object->map());
int descriptor = 0;
ZoneList<Handle<Object> > 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<Object>::null();
if (element_result == kElementFound) continue;
}
// Not an index, fallback to the slow path.
position_ = start_position;
#ifdef DEBUG
c0_ = '"';
#endif
Handle<String> key;
Handle<Object> 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<Map> 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<FieldType> 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<Object>::null();
if (element_result == kElementFound) continue;
}
// Not an index, fallback to the slow path.
position_ = start_position;
#ifdef DEBUG
c0_ = '"';
#endif
Handle<String> key;
Handle<Object> 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封装一个函数呢?可以参考下我的一个案例。(仅供参考学习)