@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
その後に正確な値を取得する場合はなおさらです。ScalaのほとんどのJSONパーサーは、O(n^2)
複雑な文字列表現用のJavaのコンストラクター(n
仮数の桁数)を使用してこれを行い、結果を安全なオプションに丸めませんMathContext
(デフォルトでは、MathContext.DECIMAL128
値はScalaのBigDecimal
コンストラクターで使用されます。操作)。
信頼できない入力を受け入れるシステムに対して、低帯域幅のDoS / DoW攻撃の下で脆弱性が発生します。以下は、クラスパスで最も人気のあるScala用JSONパーサーの最新バージョンを使用してScalaREPLで再現する方法の簡単な例です。
...
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...
最新の1Gbitネットワークの場合、1M桁の数字で悪意のあるメッセージを10ミリ秒受信すると、シングルコアで29秒の100%CPU負荷が発生する可能性があります。256を超えるコアを、全帯域幅レートで効果的にDoS処理できます。最後の式は、後続の操作+
または-
操作がScala 2.12.8で使用された場合に、13バイトの数値のメッセージを使用してCPUコアを約1.5分間書き込む方法を示しています。
そして、jsoniter-scalaは、Scala 2.11.x、2.12.x、および2.13.xのこれらすべてのケースを処理します。
なぜそれが最も効率的ですか?
以下は、128個の小さい(最大34桁の仮数)値と中程度(128桁の)の配列の解析中のさまざまなJVMでのScalaのJSONパーサーのスループット(1秒あたりの操作数が多いほど良い)結果のグラフです。仮数)BigDecimal
それに応じた値:
BigDecimal
jsoniter-scalaでの解析ルーチン:
BigDecimal
36桁までの小さな数値にコンパクトな表現の値を使用します
37〜284桁の中番号には、より効率的なホットループを使用します
O(n^1.5)
285桁を超える値の複雑さを持つ再帰的アルゴリズムに切り替えます
さらに、jsoniter-scalaは、JSONをUTF-8バイトからデータ構造に直接解析してシリアル化し、ランタイムリフレクション、中間AST、文字列、ハッシュマップを使用せずに、最小限の割り当てとコピーで非常に高速に実行します。GeoJSON、Google Maps API、OpenRTB、TwitterAPIのさまざまなデータ型と実際のメッセージサンプルの115のベンチマークの結果をここで参照してください。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加