Как разобрать JSON и выкинуть, если потеря верности? - PullRequest
1 голос
/ 12 марта 2019

Некоторые значения в JSON не могут быть представлены в JavaScript с полной точностью. Например:

9999999999999999999999999

Я работаю над протоколом / приложением, которое требует взаимодействия, и мы используем JSON в качестве нашего формата обмена данными. В моей реализации JavaScript я бы хотел, чтобы синтаксический анализатор JSON генерировал эти входные данные.

Я создал простую (и неправильную) функцию для этого.

function safeDecodeJson(str) {
  decoded = JSON.parse(str);
  reencoded = JSON.stringify(decoded);
  if (str != reencoded) {
    throw new RangeError();
  }
  return decoded;
}

Вот тестовый пример:

jsonString = "9999999999999999999999999";
safeDecodeJson(jsonString);

Выдает ошибку RangError.

Моя проблема в том, что эта функция safeDecodeJson работает, только если ввод минимален. Есть ли более надежный способ реализации этой функции?


Чтобы быть супер конкретным, я обеспокоен "неинъективной атакой" на входной файл JSON. Моя система требует, чтобы логически различные входы JSON (например, 9999999999999999999999999 и 9999999999999999999999998) имели различные представления в JavaScript. Или функция должна скинуть.

Ответы [ 3 ]

0 голосов
/ 12 марта 2019

Ответы , которые были даны, не являются ответом на вопрос. прочитайте вопрос Есть ли более надежный способ реализации этой функции? Я хотел бы спросить, можете ли вы визуализировать число при сериализации в JSON в виде строки, чтобы синтаксический анализатор JSON JavaScript не пытается разобрать это. Затем вы можете, после того, как JSON.parse вручную обработает нечетные случаи - где вы можете вручную проанализировать отдельные string d числа, а затем, возможно, сделать обратное, чтобы увидеть, совпадают ли они , Используя BigInt или что угодно, но это не то, что вы спрашивали.

0 голосов
/ 12 марта 2019

Это моя лучшая попытка, основанная на ответе на аналогичный вопрос .Он использует вашу логику преобразования в число и обратно в строку и проверки, соответствует ли она исходной строке, и применяет ее ко всем числам в JSON.

К сожалению, эта логика сама по себе немного ошибочна, так как естьнесколько способов представить одно и то же число в JSON.Он выдаст ошибку для чисел, которых нет в представлении, заданном Number#toString(), IE 1e1 является одним из способов представления 10, но он выдаст RangeError ниже.Если вы можете гарантировать, что ваши номера будут представлены в том же формате, что и Number # toString () , то это должно работать для вас:

const tests = [
  // Success cases
  `{"foo":[10,25]}`,
  `{"\\\\\\\"":[10,25]}`,
  `{"99999999999999999999999999":"99999999999999999999999999"}`,
  `{"foo":[10,25],"bar":{"baz":{"bark":[1,2,3]}}}`,

  // RangeError cases
  `{"foo":99999999999999999999999999}`,
  `{"99999999999999999999999999":99999999999999999999999999}`,
  `{"foo":[10,25],"bar":{"baz":{"bark":[1,2,3,1e1]}}}`,
];

tests.forEach( test => {
  try { 
    console.log( 'Success', JSON.stringify( safeDecodeJson( test ) ) );
  } catch ( e ) {
    console.log( 'Error', e.message );
  }
} );

function safeDecodeJson( str ) {
  const prefix = '^({{SafeJsonDecode}})^:';
  const pre_decode = str.replace( /((?:[^"]*?(?:"(?:[^"\\]|\\.)*?")?)*?)((?:[:,\[]|^)[\s\n]*)(-?(0|([1-9]\d*))(\.\d+)?([eE][-+]?\d*)?)/gsy, `$1$2"${prefix}$3"` );
  return JSON.parse( pre_decode, ( key, value ) => {
    if ( typeof value !== 'string' || ! value.startsWith( prefix ) )
      return value;
    const numeric_string = value.substr( prefix.length );
    if ( '' + +numeric_string !== numeric_string )
      throw new RangeError( `\`${numeric_string}\` out of range or not in canonical form` );
    return +numeric_string;
  } );
}

Если вы хотите, вы можете использовать библиотеку чисел произвольной точности, такую ​​как bignumber.js , для анализа чисел вместо выдачи RangeError.При этом вы также можете обнаружить, что 1e1 совпадает с 10:

const result1 = safeDecodeJson( '9999999999999999999999999' );

// If out of range the result will be a BigNumber
console.log( BigNumber.isBigNumber( result1 ) );
console.log( result1 );

const result2 = safeDecodeJson( '1e1' );

// If exactly representable by a JavaScript number, the result will be a number, not a BigNumber
console.log( BigNumber.isBigNumber( result2 ) );
console.log( result2 );

function safeDecodeJson( str ) {
  const prefix = '^({{SafeJsonDecode}})^:';
  const pre_decode = str.replace( /((?:[^"]*?(?:"(?:[^"\\]|\\.)*?")?)*?)((?:[:,\[]|^)[\s\n]*)(-?(0|([1-9]\d*))(\.\d+)?([eE][-+]?\d*)?)/gsy, `$1$2"${prefix}$3"` );
  return JSON.parse( pre_decode, ( key, value ) => {
    if ( typeof value !== 'string' || ! value.startsWith( prefix ) )
      return value;
    const numeric_string = value.substr( prefix.length );
    if ( '' + +numeric_string !== numeric_string ) {
      const big = new BigNumber( numeric_string );
      if ( ! new BigNumber( +numeric_string ).isEqualTo( big ) )
        return big;
    }
    return +numeric_string;
  } );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/8.1.1/bignumber.min.js"></script>

Вы также можете просто возвращать числа как BigNumber (просто всегда возвращайте new BigNumber( numeric_string ) вместо условного возврата +numeric_string.

0 голосов
/ 12 марта 2019

Это не совсем странно. Javascript использует плавающую точку внутри. Здесь является объяснением самого высокого значения.

Другими словами, вы не можете использовать более 53 бит. В некоторых реализациях вы можете ограничиться 31. Попробуйте использовать библиотеку bignum или, если вам нужно иметь дело только с целыми числами, библиотеку biginteger .

...