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

play.api.libs.json.JsValue.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 scala.collection._
import scala.util.hashing.MurmurHash3

case class JsResultException(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])])
    extends RuntimeException(s"JsResultException(errors:$errors)")

/**
 * Generic json value
 */
sealed trait JsValue extends JsReadable {
  def validate[A](implicit rds: Reads[A]): JsResult[A] =
    rds.reads(this)

  def validateOpt[A](implicit rds: Reads[A]): JsResult[Option[A]] =
    JsDefined(this).validateOpt[A]

  override def toString = Json.stringify(this)
}

object JsValue {
  import scala.language.implicitConversions
  implicit def jsValueToJsLookup(value: JsValue): JsLookup =
    JsLookup(JsDefined(value))
}

/**
 * Represents a Json null value.
 */
case object JsNull extends JsValue {
  @transient
  implicit val reads: Reads[JsNull.type] = Reads[JsNull.type] {
    case JsNull => JsSuccess(JsNull)
    case _      => JsError("error.expected.null")
  }
}

/**
 * Represents a Json boolean value.
 */
sealed abstract class JsBoolean(val value: Boolean) extends JsValue with Product with Serializable {
  def canEqual(that: Any): Boolean = that.isInstanceOf[JsBoolean]

  override def equals(that: Any): Boolean =
    canEqual(that) && (this.value == that.asInstanceOf[JsBoolean].value)

  override def hashCode: Int = value.hashCode
}

/**
 * Represents Json Boolean True value.
 */
case object JsTrue extends JsBoolean(true)

/**
 * Represents Json Boolean False value.
 */
case object JsFalse extends JsBoolean(false)

object JsBoolean extends (Boolean => JsBoolean) {
  def apply(value: Boolean): JsBoolean = if (value) JsTrue else JsFalse

  def unapply(b: JsBoolean): Option[Boolean] = Some(b.value)
}

/**
 * Represent a Json number value.
 */
case class JsNumber(value: BigDecimal) extends JsValue

/**
 * Represent a Json string value.
 */
case class JsString(value: String) extends JsValue

/**
 * Represent a Json array value.
 */
case class JsArray(value: IndexedSeq[JsValue] = Array[JsValue]()) extends JsValue {
  // keeping this method will also help bincompat with older play-json versions
  private[json] def this(value: collection.Seq[JsValue]) = this(value.toArray[JsValue])

  /**
   * Concatenates this array with the elements of an other array.
   */
  def ++(other: JsArray): JsArray =
    JsArray(value ++ other.value)

  /**
   * Append an element to this array.
   */
  def :+(el: JsValue): JsArray     = JsArray(value :+ el)
  def append(el: JsValue): JsArray = this.:+(el)

  /**
   * Prepend an element to this array.
   */
  def +:(el: JsValue): JsArray      = JsArray(el +: value)
  def prepend(el: JsValue): JsArray = this.+:(el)
}

object JsArray extends (IndexedSeq[JsValue] => JsArray) {
  def apply(value: collection.Seq[JsValue]) = new JsArray(value.toArray[JsValue])

  def empty = JsArray(Array.empty[JsValue])
}

/**
 * Represent a Json object value.
 */
case class JsObject(
    private[json] val underlying: Map[String, JsValue]
) extends JsValue {

  /**
   * The fields of this JsObject in the order passed to the constructor
   */
  def fields: collection.Seq[(String, JsValue)] = underlying.toSeq

  /**
   * The value of this JsObject as an immutable map.
   */
  def value: Map[String, JsValue] = underlying match {
    case m: immutable.Map[String, JsValue] => m
    case m                                 => JsObject.createFieldsMap(m)
  }

  /**
   * Return all fields as a set
   */
  def fieldSet: Set[(String, JsValue)] = underlying.toSet

  /**
   * Return all keys
   */
  def keys: Set[String] = underlying.keySet

  /**
   * Return all values
   */
  def values: Iterable[JsValue] = underlying.values

  /**
   * Merge this object with another one. Values from other override value of the current object.
   */
  def ++(other: JsObject): JsObject = JsObject(underlying ++ other.underlying)

  /**
   * Removes one field from the JsObject
   */
  def -(otherField: String): JsObject = JsObject(underlying - otherField)

  /**
   * Adds one field to the JsObject
   */
  def +(otherField: (String, JsValue)): JsObject = JsObject(underlying + otherField)

  /**
   * merges everything in depth and doesn't stop at first level, as ++ does
   */
  def deepMerge(other: JsObject): JsObject = {
    def merge(existingObject: JsObject, otherObject: JsObject): JsObject = {
      val result = existingObject.underlying ++ otherObject.underlying.map { case (otherKey, otherValue) =>
        val maybeExistingValue = existingObject.underlying.get(otherKey)

        val newValue = (maybeExistingValue, otherValue) match {
          case (Some(e: JsObject), o: JsObject) => merge(e, o)
          case _                                => otherValue
        }
        otherKey -> newValue
      }
      JsObject(result)
    }
    merge(this, other)
  }

  override def equals(other: Any): Boolean = {
    other match {
      case o: AnyRef if this.eq(o) =>
        true
      case JsObject(that) =>
        underlying == that
      case _ =>
        false
    }
  }

  def canEqual(other: Any): Boolean = other.isInstanceOf[JsObject]

  override def hashCode(): Int = MurmurHash3.unorderedHash(underlying, MurmurHash3.setSeed)
}

object JsObject extends (Seq[(String, JsValue)] => JsObject) {

  /**
   * INTERNAL API: create a fields map by wrapping a Java LinkedHashMap.
   *
   * We use this because the Java implementation better handles hash code collisions for Comparable keys.
   */
  private[json] def createFieldsMap(fields: Iterable[(String, JsValue)] = Seq.empty): immutable.Map[String, JsValue] = {
    (ImmutableLinkedHashMap.newBuilder ++= fields).result()
  }

  /**
   * Construct a new JsObject, with the order of fields in the Seq.
   */
  def apply(fields: collection.Seq[(String, JsValue)]): JsObject = new JsObject(createFieldsMap(fields))

  /** An empty JSON object */
  def empty = JsObject(Seq.empty)

  /** Identity writes */
  implicit def writes: OWrites[JsObject] = OWrites[JsObject](identity)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy