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

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

/*
 * Copyright (C) 2009-2018 Lightbend Inc. 
 */

package play.api.libs.json

/** JSON configuration */
sealed trait JsonConfiguration {
  /** Compile-time options for the JSON macros */
  type Opts <: Json.MacroOptions

  /** Naming strategy */
  def naming: JsonNaming

  /** How options are handled by the macro */
  def optionHandlers: OptionHandlers
}

object JsonConfiguration {
  type Aux[O <: Json.MacroOptions] = JsonConfiguration { type Opts = O }

  private final class Impl[O <: Json.MacroOptions](
    val naming: JsonNaming = JsonNaming.Identity,
    val optionHandlers: OptionHandlers = OptionHandlers.Default
  ) extends JsonConfiguration {
    type Opts = O

    def this(naming: JsonNaming) = this(naming, OptionHandlers.Default)
  }

  // These methods exist for binary compatibility, since Scala protected methods are public from a binary perspective.
  protected def apply(naming: JsonNaming): JsonConfiguration.Aux[Json.MacroOptions] = new Impl(naming)
  protected def default: JsonConfiguration.Aux[Json.MacroOptions] = apply()

  /**
   * @param naming the naming strategy
   * @param optionHandlers handlers for option
   */
  def apply[Opts <: Json.MacroOptions: Json.MacroOptions.Default](
    naming: JsonNaming = JsonNaming.Identity,
    optionHandlers: OptionHandlers = OptionHandlers.Default
  ): JsonConfiguration.Aux[Opts] = new Impl(naming, optionHandlers)

  /** Default configuration instance */
  implicit def default[Opts <: Json.MacroOptions: Json.MacroOptions.Default]: JsonConfiguration.Aux[Opts] = apply()
}

/**
 * Naming strategy, to map each class property to the corresponding column.
 */
trait JsonNaming extends (String => String) {
  /**
   * Returns the column name for the class property.
   *
   * @param property the name of the case class property
   */
  def apply(property: String): String
}

/** Naming companion */
object JsonNaming {

  /**
   * For each class property, use the name
   * as is for its column (e.g. fooBar -> fooBar).
   */
  object Identity extends JsonNaming {
    def apply(property: String): String = property
    override val toString = "Identity"
  }

  /**
   * For each class property, use the snake case equivalent
   * to name its column (e.g. fooBar -> foo_bar).
   */
  object SnakeCase extends JsonNaming {
    def apply(property: String): String = {
      val length = property.length
      val result = new StringBuilder(length * 2)
      var resultLength = 0
      var wasPrevTranslated = false

      for (i <- 0 until length) {
        var c = property.charAt(i)
        if (i > 0 || i != '_') {
          if (Character.isUpperCase(c)) {
            // append a underscore if the previous result wasn't translated
            if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_') {
              result.append('_')
              resultLength += 1
            }
            c = Character.toLowerCase(c)
            wasPrevTranslated = true
          } else {
            wasPrevTranslated = false
          }
          result.append(c)
          resultLength += 1
        }
      }

      // builds the final string
      result.toString()
    }

    override val toString = "SnakeCase"
  }

  /**
   * For each class property, use the pascal case equivalent
   * to name its column (e.g. fooBar -> FooBar).
   */
  object PascalCase extends JsonNaming {
    def apply(property: String): String =
      if (property.length > 0) {
        property.updated(0, Character.toUpperCase(property charAt 0))
      } else {
        property
      }

    override val toString = "PascalCase"
  }

  /** Naming using a custom transformation function. */
  def apply(transformation: String => String): JsonNaming = new JsonNaming {
    def apply(property: String): String = transformation(property)
  }
}

/** Configure how options should be handled */
trait OptionHandlers {
  def readHandler[T](jsPath: JsPath)(implicit r: Reads[T]): Reads[Option[T]]
  def writeHandler[T](jsPath: JsPath)(implicit writes: Writes[T]): OWrites[Option[T]]
  final def formatHandler[T](jsPath: JsPath)(implicit format: Format[T]): OFormat[Option[T]] = {
    OFormat(readHandler(jsPath), writeHandler(jsPath))
  }
}

/** OptionHandlers companion */
object OptionHandlers {

  /**
   * Default Option Handlers
   * Uses readNullable and writesNullable
   */
  object Default extends OptionHandlers {
    def readHandler[T](jsPath: JsPath)(implicit r: Reads[T]): Reads[Option[T]] = jsPath.readNullable
    def writeHandler[T](jsPath: JsPath)(implicit writes: Writes[T]): OWrites[Option[T]] = jsPath.writeNullable
  }

  /**
   * Option Handlers to write JsNull when handling None
   * Uses readNullable and writeOptionWithNull
   */
  object WritesNull extends OptionHandlers {
    def readHandler[T](jsPath: JsPath)(implicit reads: Reads[T]): Reads[Option[T]] = jsPath.readNullable
    def writeHandler[T](jsPath: JsPath)(implicit writes: Writes[T]): OWrites[Option[T]] = jsPath.writeOptionWithNull
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy