Когда JSON.parse «встречает» пары «не ключ-значение»

внешний интерфейс JSON JavaScript V8

предисловие

Когда json будет популярен и будет использоваться в качестве одного из основных форматов данных для внешнего и внутреннего взаимодействия, сколько людей будут иметь некоторые представления об использовании и понимании самого json и, конечно же, о преобразовании объектов json и их строковые формы будут следовать. В давние времена передняя часть использовала eval с низким энергопотреблением для преобразования формата.

Затем, исходя из здравого смысла, мы знаем, что JSON предоставляет два общих инструмента и метода для достижения взаимного преобразования, а именно JSON.parse() и JSON.stringfy(); с другой стороны, мы также знаем, что в целом мы имеем дело с объекты, возвращаемые серверной частью, имеют стандартный формат пары ключ-значение, например:{code:200,message:'success',data:{page:1,list:[]}}

Так что же происходит, когда мы конвертируем другие типы значений в бэкенде или в других сценариях? Эта идея возникла потому, что при обработке дела обнаружилось, что в бэкенде есть поле, а значение поля списка картинок возвращает '[url1, url2]', что, очевидно, является результатом строки массива. Сначала я не думал об использовании метода разбора, потому что это не были данные 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}

Отслеживание источника

Чтобы понять, почему такой результат, нам нужно проанализировать, какая логика написана внизу его метода разбора.

Основное внимание здесь уделяется анализу того, почему эти типы пар не ключ-значение поддерживаются, а некоторые нет.

Прежде всего, нам необходимо иметь общее представление о концепции: перед синтаксическим анализом String выполняется форматирование строки, чтобы убедиться, что все символы допустимы, а затем они классифицируются в соответствии с первым символом, а те, которые не соответствует ожиданиям, будет сообщено как не ожидаемая ошибка символа. Тогда он будет характерно различать, включая, помимо прочего, следующие особые случаи.

персонаж Функции вызова
{ ParseJsonObject
f определить, является ли оно ложным
t определить, верно ли
n Определить, является ли он нулевым
Содержит числа, отмеченные от 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.Когда обнаруживается, что преобразование в объект не удается, например, он оказывается нулевым, или в некоторых особых случаях будет сообщено о неожиданной строковой ошибке; Если правая сторона ], это может быть массив, который обрабатывается как простой массив и сложный массив.Простой массив задает фиксированный массив и возвращает этот массив.

// 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 внизу? Вы можете обратиться к одному из моих случаев. (только для справки)

Метод такого анализа реализован с JS: CodePen Case, чтобы быть улучшенным

Справочная документация