All Downloads are FREE. Search and download functionalities are using the official Maven repository.

play.api.libs.json.JsonConfig.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 
 */

package play.api.libs.json

import com.fasterxml.jackson.core.StreamReadConstraints
import com.typesafe.config.ConfigFactory

import play.api.libs.json.JsonConfig.defaultMaxPlain
import play.api.libs.json.JsonConfig.defaultMinPlain
import play.api.libs.json.JsonConfig.defaultDigitsLimit
import play.api.libs.json.JsonConfig.defaultMathContext
import play.api.libs.json.JsonConfig.defaultPreserveZeroDecimal
import play.api.libs.json.JsonConfig.defaultScaleLimit
import play.api.libs.json.JsonConfig.loadDigitsLimit
import play.api.libs.json.JsonConfig.loadMathContext
import play.api.libs.json.JsonConfig.loadMaxPlain
import play.api.libs.json.JsonConfig.loadMinPlain
import play.api.libs.json.JsonConfig.loadScaleLimit

import java.math.MathContext

import scala.util.control.NonFatal

/**
 * Parse and serialization settings for BigDecimals. Defines limits that will be used when parsing the BigDecimals,
 * like how many digits are accepted.
 */
sealed trait BigDecimalParseConfig {

  /**
   * The [[MathContext]] used when parsing, which will be "decimal32", "decimal64", "decimal128" (default),
   * or "unlimited".
   * This can be set using the [[JsonConfig.mathContextProperty]] system property.
   */
  def mathContext: MathContext

  /**
   * Limits the scale, and it is related to the math context used.
   * The default value is [[JsonConfig.defaultScaleLimit]].
   * This can be set using the [[JsonConfig.scaleLimitProperty]] system property.
   */
  def scaleLimit: Int

  /**
   * How many digits are accepted, also related to the math context used.
   * The default value is [[JsonConfig.defaultDigitsLimit]].
   * This can be set using the [[JsonConfig.digitsLimitProperty]] system property.
   */
  def digitsLimit: Int
}

object BigDecimalParseConfig {
  def apply(
      mathContext: MathContext = defaultMathContext,
      scaleLimit: Int = defaultScaleLimit,
      digitsLimit: Int = defaultDigitsLimit
  ): BigDecimalParseConfig = BigDecimalParseConfigImpl(mathContext, scaleLimit, digitsLimit)
}

private final case class BigDecimalParseConfigImpl(mathContext: MathContext, scaleLimit: Int, digitsLimit: Int)
    extends BigDecimalParseConfig

sealed trait BigDecimalSerializerConfig {

  /**
   * Minimum magnitude of BigDecimal to write out as a plain string.
   * Defaults to [[JsonConfig.defaultMinPlain]].
   * This can be set using the [[JsonConfig.minPlainProperty]] system property.
   */
  def minPlain: BigDecimal

  /**
   * Maximum magnitude of BigDecimal to write out as a plain string.
   * Defaults to [[JsonConfig.defaultMaxPlain]].
   * This can be set using the [[JsonConfig.maxPlainProperty]] system property.
   */
  def maxPlain: BigDecimal

  /**
   * True to preserve a zero decimal , or false to drop them (the default).
   * For example, 1.00 will be serialized as 1 if false or 1.0 if true (only a single zero is preserved).
   * Other trailing zeroes will be dropped regardless of this value.
   * For example, 1.1000 will always be serialized as 1.1.
   * This can be set using the [[JsonConfig.preserveZeroDecimalProperty]] system property.
   */
  def preserveZeroDecimal: Boolean
}

object BigDecimalSerializerConfig {
  def apply(
      minPlain: BigDecimal = defaultMinPlain,
      maxPlain: BigDecimal = defaultMaxPlain,
      preserveZeroDecimal: Boolean = defaultPreserveZeroDecimal
  ): BigDecimalSerializerConfig =
    DecimalSerializerSettingsImpl(minPlain, maxPlain, preserveZeroDecimal)
}

private final case class DecimalSerializerSettingsImpl(
    minPlain: BigDecimal,
    maxPlain: BigDecimal,
    preserveZeroDecimal: Boolean
) extends BigDecimalSerializerConfig

sealed trait JsonConfig {
  def bigDecimalParseConfig: BigDecimalParseConfig
  def bigDecimalSerializerConfig: BigDecimalSerializerConfig
  def streamReadConstraints: StreamReadConstraints
}

object JsonConfig {

  /**
   * The default math context ("decimal128").
   */
  val defaultMathContext: MathContext = MathContext.DECIMAL128

  /**
   * The default limit for the scale considering the default MathContext of decimal128.
   * limit for scale for decimal128: BigDecimal("0." + "0" * 33 + "1e-6143", java.math.MathContext.DECIMAL128).scale + 1
   */
  val defaultScaleLimit: Int = 6178

  /**
   * The default limit for digits considering the default MathContext of decimal128.
   * 307 digits should be the correct value for 128 bytes. But we are using 310
   * because Play JSON uses BigDecimal to parse any number including Doubles and
   * Doubles max value has 309 digits, so we are using 310 here
   */
  val defaultDigitsLimit: Int = 310

  /**
   * Zero decimal values (e.g. .0 or .00) or dropped by default.
   * For example, a value of 1.0 or 1.00 will be serialized as 1.
   */
  val defaultPreserveZeroDecimal: Boolean = false

  /**
   * The default maximum magnitude of BigDecimal to write out as a plain string.
   */
  val defaultMaxPlain: BigDecimal = 1E20

  /**
   * The default minimum magnitude of BigDecimal to write out as a plain string.
   */
  val defaultMinPlain: BigDecimal = 1E-10

  /**
   * The system property to override the scale limit.
   */
  val scaleLimitProperty: String = "play.json.parser.scaleLimit"

  /**
   * The system property to override the digits limit
   */
  val digitsLimitProperty: String = "play.json.parser.digitsLimit"

  /**
   * The system property to override the math context. This can be "decimal32", "decimal64", "decimal128" (the default),
   * or "unlimited".
   */
  val mathContextProperty: String = "play.json.parser.mathContext"

  /**
   * The system property to override the minimum magnitude of BigDecimal to write out as a plain string
   */
  val minPlainProperty: String = "play.json.serializer.minPlain"

  /**
   * The system property to override the maximum magnitude of BigDecimal to write out as a plain string
   */
  val maxPlainProperty: String = "play.json.serializer.maxPlain"

  /**
   * The system property to override the max nesting depth for JSON parsing.
   */
  val maxNestingDepth: String = "play.json.parser.maxNestingDepth"

  /**
   * The system property to override the max string length for JSON parsing.
   * This is used to limit the length of individual strings in JSON documents.
   */
  val maxStringLength: String = "play.json.parser.maxStringLength"

  /**
   * The system property to override whether zero decimals (e.g. .0 or .00) are written by default. These are dropped by default.
   */
  val preserveZeroDecimalProperty: String = "play.json.serializer.preserveZeroDecimal"

  private[json] val playJsonConfig = ConfigFactory
    .load()
    .getConfig("play.json.jackson")

  private[json] def loadScaleLimit: Int = prop(scaleLimitProperty, defaultScaleLimit)(_.toInt)

  private[json] def loadDigitsLimit: Int = prop(digitsLimitProperty, defaultDigitsLimit)(_.toInt)

  private[json] def loadMathContext: MathContext = parseMathContext(mathContextProperty)

  private[json] def loadMinPlain: BigDecimal = prop(minPlainProperty, defaultMinPlain)(BigDecimal.exact)

  private[json] def loadMaxPlain: BigDecimal = prop(maxPlainProperty, defaultMaxPlain)(BigDecimal.exact)

  private[json] def loadMaxNestingDepth: Int =
    prop(maxNestingDepth, playJsonConfig.getInt("read.max-nesting-depth"))(Integer.parseInt)

  private[json] def loadMaxStringLength: Int =
    prop(maxStringLength, playJsonConfig.getInt("read.max-string-length"))(Integer.parseInt)

  private[json] def loadPreserveZeroDecimal: Boolean =
    prop(preserveZeroDecimalProperty, defaultPreserveZeroDecimal)(_.toBoolean)

  private[json] val defaultStreamReadConstraints: StreamReadConstraints =
    StreamReadConstraints
      .builder()
      .maxNestingDepth(loadMaxNestingDepth)
      .maxStringLength(loadMaxStringLength)
      .maxNumberLength(Int.MaxValue) // play-json has its own support for limiting number length
      .build()

  // Default settings, which can be controlled with system properties.
  // To override, call JacksonJson.setConfig()
  val settings: JsonConfig =
    JsonConfig(
      BigDecimalParseConfig(loadMathContext, loadScaleLimit, loadDigitsLimit),
      BigDecimalSerializerConfig(loadMinPlain, loadMaxPlain, loadPreserveZeroDecimal),
      defaultStreamReadConstraints
    )

  def apply(): JsonConfig = apply(BigDecimalParseConfig(), BigDecimalSerializerConfig())

  def apply(
      bigDecimalParseConfig: BigDecimalParseConfig,
      bigDecimalSerializerConfig: BigDecimalSerializerConfig
  ): JsonConfig =
    JsonConfigImpl(bigDecimalParseConfig, bigDecimalSerializerConfig, defaultStreamReadConstraints)

  def apply(
      bigDecimalParseConfig: BigDecimalParseConfig,
      bigDecimalSerializerConfig: BigDecimalSerializerConfig,
      streamReadConstraints: StreamReadConstraints
  ): JsonConfig =
    JsonConfigImpl(bigDecimalParseConfig, bigDecimalSerializerConfig, streamReadConstraints)

  private[json] def parseMathContext(key: String): MathContext = sys.props.get(key).map(_.toLowerCase) match {
    case Some("decimal128") => MathContext.DECIMAL128
    case Some("decimal64")  => MathContext.DECIMAL64
    case Some("decimal32")  => MathContext.DECIMAL32
    case Some("unlimited")  => MathContext.UNLIMITED
    case _                  => defaultMathContext
  }

  private[json] def prop[T](key: String, default: T)(f: String => T): T =
    try {
      sys.props.get(key).map(f).getOrElse(default)
    } catch {
      case NonFatal(_) => default
    }
}

private final case class JsonConfigImpl(
    bigDecimalParseConfig: BigDecimalParseConfig,
    bigDecimalSerializerConfig: BigDecimalSerializerConfig,
    streamReadConstraints: StreamReadConstraints
) extends JsonConfig

@deprecated("Use BigDecimalParseConfig instead", "2.9.4")
final case class BigDecimalParseSettings(
    mathContext: MathContext = MathContext.DECIMAL128,
    scaleLimit: Int,
    digitsLimit: Int
) extends BigDecimalParseConfig

@deprecated("Use BigDecimalSerializerConfig instead", "2.9.4")
final case class BigDecimalSerializerSettings(
    minPlain: BigDecimal,
    maxPlain: BigDecimal
) extends BigDecimalSerializerConfig {
  override def preserveZeroDecimal: Boolean = defaultPreserveZeroDecimal
}

@deprecated("Use JsonConfig instead", "2.9.4")
final case class JsonParserSettings(
    bigDecimalParseSettings: BigDecimalParseSettings,
    bigDecimalSerializerSettings: BigDecimalSerializerSettings,
    streamReadConstraints: StreamReadConstraints = JsonConfig.defaultStreamReadConstraints
) extends JsonConfig {
  override def bigDecimalParseConfig: BigDecimalParseConfig = bigDecimalParseSettings

  override def bigDecimalSerializerConfig: BigDecimalSerializerConfig = bigDecimalSerializerSettings
}

object JsonParserSettings {
  val defaultMathContext: MathContext = JsonConfig.defaultMathContext

  // Limit for the scale considering the MathContext of 128
  // limit for scale for decimal128: BigDecimal("0." + "0" * 33 + "1e-6143", java.math.MathContext.DECIMAL128).scale + 1
  val defaultScaleLimit: Int = JsonConfig.defaultScaleLimit

  // 307 digits should be the correct value for 128 bytes. But we are using 310
  // because Play JSON uses BigDecimal to parse any number including Doubles and
  // Doubles max value has 309 digits, so we are using 310 here
  val defaultDigitsLimit: Int = JsonConfig.defaultDigitsLimit

  // Maximum magnitude of BigDecimal to write out as a plain string
  val MaxPlain: BigDecimal = JsonConfig.defaultMaxPlain

  // Minimum magnitude of BigDecimal to write out as a plain string
  val MinPlain: BigDecimal = JsonConfig.defaultMinPlain

  def apply(): JsonParserSettings = JsonParserSettings(
    BigDecimalParseSettings(defaultMathContext, defaultScaleLimit, defaultDigitsLimit),
    BigDecimalSerializerSettings(minPlain = MinPlain, maxPlain = MaxPlain)
  )

  /**
   * Return the default settings configured from System properties.
   */
  val settings: JsonParserSettings = {
    JsonParserSettings(
      BigDecimalParseSettings(loadMathContext, loadScaleLimit, loadDigitsLimit),
      BigDecimalSerializerSettings(loadMinPlain, loadMaxPlain)
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy