preface

When JSON became popular as one of the main data formats for communication between the front and back ends, everyone had some idea of the use and understanding of JSON itself, as well as the conversion of JSON objects and their string form. In the ancient past, the front-end implemented format conversions through low-power EVAL.

Parse () and json.stringfy (); On the other side of common sense, we also know that in general the objects we deal with back ends return standard key-value pairs, such as {code:200,message:’success’,data:{page:1,list:[]}}

What happens when we convert other value types on the back end or in other scenarios? I came up with the idea when I was doing business and found that there was a field on the back end. The field value of the image list returned ‘[URL1, urL2]’, which was obviously the result of an array string. I didn’t think of using parse at first because I was limited to the fact that this wasn’t JSON data.

What is JSON data

We know that JSON is a subset of THE JS object representation, and its standard definition has the following rules:

  • The data is in name, value pairs
  • Data is separated by commas
  • Curly braces save objects
  • Square brackets hold arrays

Are common data types like strings, Booleans, null,undefined, numbers, and functions, objects, and arrays of reference types json? Or does stringification support conversion?

I have done some case verification, and I will directly publish the results here. If you are interested, you can check whether this is the result.

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 inParse (JSON at position 3)'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}')//Uncaught SyntaxError: Unexpected token d in JSON at position 1
JSON.parse('{"name":1}')//{name:1}
Copy the code

Their roots

To understand why this is the case, we need to examine what logic is written underneath the parse method.

The focus here is on why these types of non-key-value pairs are supported and why some are not.

First, we need to understand the basic concept: the String is parsed before a String formatting, to ensure that the whole character is valid, and then according to the first character classification, does not meet the expected situation will be unexpected character error. It then characteristically differentiates including but not limited to the following special cases.

character Call a function
{ ParseJsonObject
f Check whether it is false
t Check whether it is true
n Check whether it is null
Contains a number, starting with 0-9 or a negative number Check to see if the whole thing is a number

Source tracking: overall logic

We found the following code from the source view: you need to climb the wall to see it. The corresponding source file address is:

  • Chromium.googlesource.com/v8/v8.git/+…
  • Chromium.googlesource.com/v8/v8.git/+…
ParseJsonString = ParseJsonString = ParseJsonStringif (c0_ == '"') returnParseJsonString(); // Case 2: it is found that the number starts from 0 to 9, or it starts from -. It is possible that the number type is converted to a numberif ((c0_ >= '0' && c0_ <= '9') || c0_ == The '-') returnParseJsonNumber(); // The discovery starts with the left tag {, using json object parsing methodif (c0_ == '{') returnParseJsonObject(); // The discovery is beginning, try to use the array conversion method to convertif (c0_ == '[') returnParseJsonArray(); // Case 5: Exclude some special data types, such astrue.false, the stringing of nullif (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();
  }

Copy the code

Source tracing: A concrete approach

ParseJsonString

Because the language of this part of the code itself did not go to learn, a simple analysis of some of the logic inside the processing, mainly dealing with the content is a single word section, as well as some special symbol processing.

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
Copy the code

ParseJsonArray

The core is to handle if the end of an array is]. The premise of array processing is that the right terminator must be]. If not, the ParseJsonValue will be converted, and when the conversion to an object fails, such as null, or some special case, an unexpected string error will be reported. If the right hand side is], then it could be an array, and you treat it as a simple array and a complex array, and the simple array will specify a fixed array and then return that array.

// 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
Copy the code

ParseJsonNumber

The core determines the handling of some negative numbers, 0, 1-9, decimal points and other different cases, and throws exception characters for cases that do not meet the requirements.

template <bool seq_one_byte>
Handle<Object> JsonParser<seq_one_byte>::ParseJsonNumber() {
  bool negative = false;
  int beg_pos = position_;
  if (c0_ == The '-') {
    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
           
          
         
        
       
      Copy the code

ParseJsonObject

The core checks whether the end of the string is} to ensure that the JSON object is intact, and strictly checks whether the basic format of the key-value pair is rechecked.

// 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()) returnReportUnexpectedCharacter(); 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()) returnReportUnexpectedCharacter(); JSObject::DefinePropertyOrElementIgnoreAttributes(json_object, key, value) .Check(); }}if(c0_ ! ='} ') {
      return ReportUnexpectedCharacter();
    }
  }
  AdvanceSkipWhitespace();
  return scope.CloseAndEscape(json_object);
}

ParseJsonObject
Copy the code

My method rewrite

Assuming the browser doesn’t support these methods, how do we wrap a function in JS underneath? Consider one of my cases. (For reference study only)

The parse method is implemented in JS: Codepen case, to be perfected

Reference documentation

  • Json strategy

  • How does Chrome V8 implement json.parse