前言
在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封裝一個函式呢?可以參考下我的一個案例。(僅供參考學習)