@ Saurabh Какой хороший вопрос! Очень важно, чтобы вы поделились прецедентом!
Я думаю, что мой ответ позволяет решить его наиболее безопасным и эффективным способом ... В краткой форме это:
Используйте jsoniter-scala для точного анализа BigDecimal
значений.
Кодирование / декодирование в / из строк JSON для любого числового типа может быть определено для каждого кодека или для каждого поля класса. Пожалуйста, см. Код ниже:
1) Добавьте зависимости в ваш build.sbt
:
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.0.1",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.0.1" % Provided // required only in compile-time
)
2) Определите структуры данных, выведите кодек для корневой структуры, проанализируйте тело ответа и сериализуйтеобратно:
import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._
case class Response(
amount: BigDecimal,
@stringified price: BigDecimal)
implicit val codec: JsonValueCodec[Response] = JsonCodecMaker.make {
CodecMakerConfig
.withIsStringified(false) // switch it on to stringify all numeric and boolean values in this codec
.withBigDecimalPrecision(34) // set a precision to round up to decimal128 format: java.math.MathContext.DECIMAL128.getPrecision
.withBigDecimalScaleLimit(6178) // limit scale to fit the decimal128 format: BigDecimal("0." + "0" * 33 + "1e-6143", java.math.MathContext.DECIMAL128).scale + 1
.withBigDecimalDigitsLimit(308) // limit a number of mantissa digits to be parsed before rounding with the specified precision
}
val response = readFromArray("""{"amount":1000,"price":"1.20"}""".getBytes("UTF-8"))
val json = writeToArray(Response(amount = BigDecimal(1000), price = BigDecimal("1.20")))
3) Распечатайте результаты на консоль и проверьте их:
println(response)
println(new String(json, "UTF-8"))
Response(1000,1.20)
{"amount":1000,"price":"1.20"}
Почему предложенный подход безопасен?
Ну ... Разбор JSON - это минное поле , особенно если после этого у вас будут точные значения BigDecimal
. Большинство анализаторов JSON для Scala делают это, используя конструктор Java для строкового представления, который имеет O(n^2)
сложность (где n
- это число цифр в мантиссе) и не округляет результаты до безопасного параметра MathContext
(по умолчаниюЗначение MathContext.DECIMAL128
используется для этого в BigDecimal
конструкторах и операциях Scala).
Внедряет уязвимости при DoS / DoW-атаках с низкой пропускной способностью для систем, которые принимают ненадежный ввод. Ниже приведен простой пример того, как его можно воспроизвести в Scala REPL с последней версией самого популярного парсера JSON для Scala в classpath:
...
Starting scala interpreter...
Welcome to Scala 2.12.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> def timed[A](f: => A): A = { val t = System.currentTimeMillis; val r = f; println(s"Elapsed time (ms): ${System.currentTimeMillis - t}"); r }
timed: [A](f: => A)A
scala> timed(io.circe.parser.decode[BigDecimal]("9" * 1000000))
Elapsed time (ms): 29192
res0: Either[io.circe.Error,BigDecimal] = Right(999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999...
scala> timed(io.circe.parser.decode[BigDecimal]("1e-100000000").right.get + 1)
Elapsed time (ms): 87185
res1: scala.math.BigDecimal = 1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000...
Для современных сетей 1 Гбит 10 мс получать вредоносное сообщение с1-значное число может дать 29 секунд 100% загрузки процессора на одно ядро. Более 256 ядер могут быть эффективно DoS-ed на полной скорости полосы пропускания. Последнее выражение демонстрирует, как записать ядро ЦП в течение ~ 1,5 минут, используя сообщение с 13-байтовым номером, если последующие операции +
или -
были использованы с Scala 2.12.8.
И, jsoniter-scala позаботьтесь обо всех этих случаях для Scala 2.11.x, 2.12.x и 2.13.x.
Почему это наиболее эффективно?
Ниже приведеныдиаграммы с пропускной способностью (операций в секунду, поэтому чем больше, тем лучше) результаты синтаксических анализаторов JSON для Scala для разных JVM при разборе массива из 128 малых (до 34-значных мантисс) значений и среднего (со 128-значной мантиссой)значение BigDecimal
соответственно:
Процедура синтаксического анализа дляBigDecimal
в jsoniter-scala:
использует BigDecimal
значения с компактным представлением для небольших чисел до 36 цифр
использует более эффективные горячие циклы для средних чисел, которые имеют от 37 до 284 цифр
swжаждет рекурсивного алгоритма, который имеет O(n^1.5)
сложность для значений, которые имеют более 285 цифр
Более того, jsoniter-scala анализирует и сериализует JSON напрямую из байтов UTF-8 в ваши структуры данныхи обратно, и делает это безумно быстро без использования отражения во время выполнения, промежуточных AST, строк или хэш-карт, с минимальными выделениями и копированием. См. здесь результаты 115 тестов для различных типов данных и реальных примеров сообщений для GeoJSON, Google Maps API, OpenRTB и Twitter API.