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

com.rojoma.json.v3.ast.ast.scala Maven / Gradle / Ivy

The newest version!
package com.rojoma.json.v3
package ast

import scala.language.implicitConversions
import scala.{collection => sc}
import scala.reflect.ClassTag
import java.math.{BigInteger, BigDecimal => JBigDecimal}
import java.io.Writer

import extensions.DoubleWriter

sealed abstract class JsonInvalidValue(msg: String) extends IllegalArgumentException(msg) {
  def value: Any
}
case class JsonInvalidFloat(value: Float) extends JsonInvalidValue("Attempted to place a NaN or infinite value into a JSON AST.")
case class JsonInvalidDouble(value: Double) extends JsonInvalidValue("Attempted to place a NaN or infinite value into a JSON AST.")

// Closed typeclass to find out what concrete types a subclass of JValue represents
sealed abstract class Json[T <: JValue] {
  val jsonTypes : Set[JsonType]
  override lazy val toString = jsonTypes.toSeq.map(_.toString).sorted.mkString(",")
}

sealed trait JsonType
object JsonType {
  import codec._
  implicit val jCodec: JsonEncode[JsonType] with JsonDecode[JsonType] = new JsonEncode[JsonType] with JsonDecode[JsonType] {
    def encode(j: JsonType) = JString(j.toString)
    def decode(x: JValue) = x match {
      case JString(JObject.toString) => Right(JObject)
      case JString(JArray.toString) => Right(JArray)
      case JString(JString.toString) => Right(JString)
      case JString(JNumber.toString) => Right(JNumber)
      case JString(JBoolean.toString) => Right(JBoolean)
      case JString(JNull.toString) => Right(JNull)
      case s: JString => Left(DecodeError.InvalidValue(s))
      case other => Left(DecodeError.InvalidType(other.jsonType, JString))
    }
  }
}

/** A JSON datum.  This can be safely downcast to a more-specific type
  * using the `cast` method which is implicitly added to this class
  * in the companion object.*/
sealed trait JValue {
  override def toString = io.PrettyJsonWriter.toString(this)

  /** Forces this [[com.rojoma.json.v3.ast.JValue]] to be fully
    * evaluated.  In particular, the compound
    * [[com.rojoma.json.v3.codec.JsonEncode]]s will produce views of their
    * inputs instead of fully-evaluated
    * [[com.rojoma.json.v3.ast.JValue]]s.  This can be problematic if
    * the underlying structure can be mutated before this object is
    * used, or if this object is passed to another thread.
    *
    * What is or is not copied is not defined; the only postcondition
    * is that there are no lazy values left in the returned tree.
    * 
    * @return An equal [[com.rojoma.json.v3.ast.JValue]] with strict values. */
  def forced: JValue

  /** Produces a dynamically typed view of this `JValue` which can be
    * descended using dot-notation for field names or apply-type
    * syntax for arrays.  It can be turned back into a `JValue` with
    * the `!` or `?` methods.
    *
    * Note that certain field-names (the names common to all objects
    * plus `apply`, `applyDynamic`, and `selectDynamic` cannot be accessed
    * with simple field-notation.  Instead, pass them as strings to
    * the `apply` method. */
  @deprecated(message = "Prefer `dyn`", since = "3.1.1")
  def dynamic = new com.rojoma.json.v3.dynamic.DynamicJValue(Some(this))

  /** Produces a dynamically typed view of this `JValue` which can be
    * descended using dot-notation for field names or apply-type
    * syntax for arrays.  It can be turned back into a `JValue` with
    * the `!` or `?` methods.
    *
    * Note that certain field-names (the names common to all `Objects`
    * plus `apply`, `applyDynamic`, and `selectDynamic` cannot be accessed
    * with simple field-notation.  Instead, pass them as strings to
    * the `apply` method. */
  def dyn = com.rojoma.json.v3.dynamic.InformationalDynamicJValue(this)

  /* The concrete type of this value. */
  def jsonType: JsonType
}

object JValue {
  final override val toString = "value"

  /** Safe downcast with a fairly nice syntax.
    * This will statically prevent attempting to cast anywhere except
    * a subclass of the value's static type.
    *
    * It can be used for navigation in a JSON tree:
    * {{{
    *   for {
    *     JObject(foo) <- raw.cast[JObject]
    *     JArray(elems) <- foo.get("foo").flatMap(_.cast[JArray])
    *   } yield {
    *     ...something with elems...
    *   } getOrElse(throw "couldn't find interesting elements")
    * }}}
    */
  implicit def toCastable[T <: JValue](x: T) = new `-impl`.ast.DownCaster(x)

  implicit object Concrete extends Json[JValue] {
    val jsonTypes = JAtom.Concrete.jsonTypes ++ JCompound.Concrete.jsonTypes
  }
}

/** A JSON "atom" — anything except arrays or objects.  This and [[com.rojoma.json.v3.ast.JCompound]] form
  * a partition of the set of valid [[com.rojoma.json.v3.ast.JValue]]s. */
sealed abstract class JAtom extends JValue {
  final def forced: this.type = this
}

object JAtom {
  final override val toString = "atom"

  implicit object Concrete extends Json[JAtom] {
    val jsonTypes = Set[JsonType](JNumber, JString, JBoolean, JNull)
  }
}

/** A number. */
sealed abstract class JNumber extends JAtom {
  def toByte: Byte
  def toShort: Short
  def toInt: Int
  def toLong: Long
  def toBigInt: BigInt
  def toBigInteger: BigInteger
  def toFloat: Float
  def toDouble: Double
  def toBigDecimal: BigDecimal
  def toJBigDecimal: JBigDecimal

  final def jsonType = JNumber

  override final def toString = asString
  protected def asString: String
  private[v3] def toWriter(w: Writer) = w.write(asString)

  override def equals(o: Any) = o match {
    case that: JNumber => this.toJBigDecimal == that.toJBigDecimal
    case _ => false
  }
  override final lazy val hashCode = toJBigDecimal.hashCode
}

object JNumber extends JsonType {
  final override val toString = "number"

  private val stdCtx = java.math.MathContext.UNLIMITED

  private val doubleWriter = DoubleWriter.doubleWriter

  private class JIntNumber(val toInt: Int) extends JNumber {
    def toByte = toInt.toByte
    def toShort = toInt.toShort
    def toLong = toInt.toLong
    def toBigInt = BigInt(toInt)
    def toBigInteger = BigInteger.valueOf(toInt)

    def toFloat = toInt.toFloat
    def toDouble = toInt.toDouble
    def toBigDecimal = BigDecimal(toInt, stdCtx)
    lazy val toJBigDecimal = new JBigDecimal(toInt, stdCtx)

    def asString = toInt.toString

    override def equals(o: Any) = o match {
      case that: JIntNumber => this.toInt == that.toInt
      case that: JLongNumber => this.toLong == that.toLong
      case that: JBigIntNumber => this.toBigInt == that.toBigInt
      case that: JBigIntegerNumber => this.toBigInteger == that.toBigInteger
      case other => super.equals(other)
    }
  }

  private object JIntNumber {
    private val preallocated = {
      val tmp = new Array[JIntNumber](256)
      for(i <- 0 until 256) {
        tmp(i) = new JIntNumber(i - 128)
      }
      tmp
    }

    def create(n: Int) = {
      if(n >= -128 && n < 128) preallocated(n + 128)
      else new JIntNumber(n)
    }
  }

  private class JLongNumber(val toLong: Long) extends JNumber {
    def toByte = toLong.toByte
    def toShort = toLong.toShort
    def toInt = toLong.toInt
    def toBigInt = BigInt(toLong)
    def toBigInteger = BigInteger.valueOf(toLong)

    def toFloat = toLong.toFloat
    def toDouble = toLong.toDouble
    def toBigDecimal = BigDecimal(toLong, stdCtx)
    lazy val toJBigDecimal = new JBigDecimal(toLong, stdCtx)

    def asString = toLong.toString

    override def equals(o: Any) = o match {
      case that: JIntNumber => this.toLong == that.toLong
      case that: JLongNumber => this.toLong == that.toLong
      case that: JBigIntNumber => this.toBigInt == that.toBigInt
      case that: JBigIntegerNumber => this.toBigInt == that.toBigInt
      case _ => super.equals(o)
    }
  }

  private object JLongNumber {
    def create(n: Long) = {
      if(n >= Int.MinValue && n <= Int.MaxValue) JIntNumber.create(n.toInt)
      else new JLongNumber(n)
    }
  }

  private class JBigIntNumber(val toBigInt: BigInt) extends JNumber {
    def toByte: Byte = toBigInt.toByte
    def toShort: Short = toBigInt.toShort
    def toInt: Int = toBigInt.toInt
    def toLong: Long = toBigInt.toLong
    def toBigInteger: BigInteger = toBigInt.underlying()

    def toFloat: Float = toBigInt.toFloat
    def toDouble: Double = toBigInt.toDouble
    def toBigDecimal = BigDecimal(toBigInt, stdCtx)
    def toJBigDecimal = new JBigDecimal(toBigInteger, stdCtx)

    def asString = toBigInt.toString

    override def equals(o: Any) = o match {
      case that: JIntNumber => this.toBigInt == that.toBigInt
      case that: JLongNumber => this.toBigInt == that.toBigInt
      case that: JBigIntNumber => this.toBigInt == that.toBigInt
      case that: JBigIntegerNumber => this.toBigInt == that.toBigInt
      case other => super.equals(other)
    }
  }

  private object JBigIntNumber {
    val lowerBound = BigInt(Long.MinValue)
    val upperBound = BigInt(Long.MaxValue)
    def create(n: BigInt) = {
      if(n >= lowerBound && n <= upperBound) JLongNumber.create(n.toLong)
      else new JBigIntNumber(n)
    }
  }

  private class JBigIntegerNumber(val toBigInteger: BigInteger) extends JNumber {
    def toByte: Byte = toBigInteger.byteValue
    def toShort: Short = toBigInteger.shortValue
    def toInt: Int = toBigInteger.intValue
    def toLong: Long = toBigInteger.longValue
    def toBigInt: BigInt = BigInt(toBigInteger)

    def toFloat: Float = toBigInteger.floatValue
    def toDouble: Double = toBigInteger.doubleValue
    def toBigDecimal = BigDecimal(toBigInt, stdCtx)
    lazy val toJBigDecimal = new JBigDecimal(toBigInteger, stdCtx)

    def asString = toBigInt.toString

    override def equals(o: Any) = o match {
      case that: JIntNumber => this.toBigInteger == that.toBigInteger
      case that: JLongNumber => this.toBigInteger == that.toBigInteger
      case that: JBigIntNumber => this.toBigInteger == that.toBigInteger
      case that: JBigIntegerNumber => this.toBigInteger == that.toBigInteger
      case other => super.equals(other)
    }
  }

  private object JBigIntegerNumber {
    val lowerBound = BigInteger.valueOf(Long.MinValue)
    val upperBound = BigInteger.valueOf(Long.MaxValue)
    def create(n: BigInteger) = {
      if(n.compareTo(lowerBound) >= 0 && n.compareTo(upperBound) <= 0) JLongNumber.create(n.longValue)
      else new JBigIntegerNumber(n)
    }
  }

  private class JFloatNumber(val toFloat: Float) extends JNumber {
    if(toFloat.isNaN || toFloat.isInfinite) throw JsonInvalidFloat(toFloat)

    def toByte = toFloat.toByte
    def toShort: Short = toFloat.toShort
    def toInt: Int = toFloat.toInt
    def toLong: Long = toFloat.toLong
    def toBigInt: BigInt = toBigDecimal.toBigInt
    def toBigInteger: BigInteger = toJBigDecimal.toBigInteger

    def toDouble: Double = toFloat.toDouble
    def toBigDecimal = BigDecimal(toDouble, stdCtx)
    lazy val toJBigDecimal = new JBigDecimal(toDouble, stdCtx)

    def asString = toFloat.toString
  }

  private class JDoubleNumber(val toDouble: Double) extends JNumber {
    if(toDouble.isNaN || toDouble.isInfinite) throw JsonInvalidDouble(toDouble)

    def toByte: Byte = toDouble.toByte
    def toShort: Short = toDouble.toShort
    def toInt: Int = toDouble.toInt
    def toLong: Long = toDouble.toLong
    def toBigInt: BigInt = toBigDecimal.toBigInt
    def toBigInteger: BigInteger = toJBigDecimal.toBigInteger

    def toFloat: Float = toDouble.toFloat
    def toBigDecimal = BigDecimal(toDouble, stdCtx)
    lazy val toJBigDecimal = new JBigDecimal(toDouble, stdCtx)

    def asString = doubleWriter.toString(toDouble)
    override def toWriter(w: Writer) = doubleWriter.toWriter(w, toDouble)
  }

  private class JBigDecimalNumber(val toBigDecimal: BigDecimal) extends JNumber {
    def toByte: Byte = toBigDecimal.toByte
    def toShort: Short = toBigDecimal.toShort
    def toInt: Int = toBigDecimal.toInt
    def toLong: Long = toBigDecimal.toLong
    def toBigInt: BigInt = toBigDecimal.toBigInt
    def toBigInteger: BigInteger = toJBigDecimal.toBigInteger

    def toFloat: Float = toBigDecimal.toFloat
    def toDouble: Double = toBigDecimal.toDouble
    lazy val toJBigDecimal = toBigDecimal.underlying()

    def asString = toBigDecimal.toString
  }

  private class JJBigDecimalNumber(val toJBigDecimal: JBigDecimal) extends JNumber {
    def toByte: Byte = toJBigDecimal.byteValue
    def toShort: Short = toJBigDecimal.shortValue
    def toInt: Int = toJBigDecimal.intValue
    def toLong: Long = toJBigDecimal.longValue
    def toBigInt: BigInt = BigInt(toBigInteger)
    def toBigInteger: BigInteger = toJBigDecimal.toBigInteger

    def toFloat: Float = toBigDecimal.floatValue
    def toDouble: Double = toBigDecimal.doubleValue
    def toBigDecimal: BigDecimal = BigDecimal(toJBigDecimal)

    def asString = toJBigDecimal.toString
  }

  private class JUncheckedStringNumber(override val asString: String) extends JNumber {
    def toByte: Byte = try { asString.toByte} catch { case _: NumberFormatException => toBigDecimal.toByte }
    def toShort: Short = try { asString.toShort } catch { case _: NumberFormatException => toBigDecimal.toShort }
    def toInt: Int = try { asString.toInt } catch { case _: NumberFormatException => toBigDecimal.toInt }
    def toLong: Long = try { asString.toLong } catch { case _: NumberFormatException => toBigDecimal.toLong }
    def toBigInt = try { BigInt(asString) } catch { case _: NumberFormatException => toBigDecimal.toBigInt }
    def toBigInteger = try { new BigInteger(asString) } catch { case _: NumberFormatException => toJBigDecimal.toBigInteger }

    def toFloat: Float = try { asString.toFloat } catch { case _: NumberFormatException => toBigDecimal.toFloat }
    def toDouble: Double = try { asString.toDouble } catch { case _: NumberFormatException => toBigDecimal.toDouble }
    def toBigDecimal = BigDecimal(asString, stdCtx)
    lazy val toJBigDecimal = new JBigDecimal(asString, stdCtx)
  }

  def apply(b: Byte): JNumber = JIntNumber.create(b)
  def apply(s: Short): JNumber = JIntNumber.create(s)
  def apply(i: Int): JNumber = JIntNumber.create(i)
  def apply(l: Long): JNumber = JLongNumber.create(l)
  def apply(bi: BigInt): JNumber = JBigIntNumber.create(bi)
  def apply(bi: BigInteger): JNumber = JBigIntegerNumber.create(bi)
  def apply(f: Float): JNumber = new JFloatNumber(f)
  def apply(d: Double): JNumber = new JDoubleNumber(d)
  def apply(bd: BigDecimal): JNumber = new JBigDecimalNumber(bd)
  def apply(bd: JBigDecimal): JNumber = new JJBigDecimalNumber(bd)

  def unsafeFromString(s: String): JNumber = new JUncheckedStringNumber(s)

  implicit object Concrete extends Json[JNumber] {
    val jsonTypes = Set[JsonType](JNumber)
  }
}

/** A JSON string.  This does not yet enforce well-formedness with
  * respect to surrogate pairs, but it probably should. */
case class JString(string: String) extends JAtom {
  def jsonType = JString
}

object JString extends scala.runtime.AbstractFunction1[String, JString] with JsonType {
  override final val toString = "string"

  implicit object Concrete extends Json[JString] {
    val jsonTypes = Set[JsonType](JString)
  }
}

/** A boolean */
case class JBoolean(boolean: Boolean) extends JAtom {
  def jsonType = JBoolean
}

object JBoolean extends `-impl`.ast.JBooleanApply with JsonType {
  val canonicalTrue = new JBoolean(true)
  val canonicalFalse = new JBoolean(false)

  override final val toString = "boolean"

  implicit object Concrete extends Json[JBoolean] {
    val jsonTypes = Set[JsonType](JBoolean)
  }
}

/** Null. */
sealed abstract class JNull extends JAtom // so the object has a nameable type
case object JNull extends JNull with JsonType {
  final override val toString = "null"

  def jsonType = JNull

  implicit object Concrete extends Json[JNull] {
    val jsonTypes = Set[JsonType](JNull)
  }
}

/** The common superclass of arrays and objects.  This and [[com.rojoma.json.v3.ast.JAtom]] form
  * a partition of the set of valid [[com.rojoma.json.v3.ast.JValue]]s. */
sealed trait JCompound extends JValue {
  def forced: JCompound
  def size: Int
}

object JCompound {
  final override val toString = "compound"

  implicit object Concrete extends Json[JCompound] {
    val jsonTypes = JArray.Concrete.jsonTypes ++ JObject.Concrete.jsonTypes
  }
}

/** A JSON array, implemented as a thin wrapper around a sequence of [[com.rojoma.json.v3.ast.JValue]]s.
  * In many ways this can be treated as a `Seq`, but it is in fact not one. */
case class JArray(elems: sc.Seq[JValue]) extends Iterable[JValue] with PartialFunction[Int, JValue] with JCompound {
  import `-impl`.ast.AnnoyingJArrayHack._

  override def size = elems.size
  def length = elems.length
  override def toList = elems.toList
  override def toStream = elems.toStream
  override def toVector = elems.toVector
  override def toArray[B >: JValue : ClassTag] = elems.toArray[B]

  def apply(idx: Int) = toSeq(idx)
  def isDefinedAt(idx: Int) = toSeq.isDefinedAt(idx)
  def iterator = elems.iterator

  override def toSeq = elems.toSeq
  override def toIndexedSeq = elems.toIndexedSeq

  def forced: JArray = {
    // not just "toSeq.map(_forced)" because the seq might be a Stream or view
    val forcedArray: Vector[JValue] =
      convertForForce(elems).map(_.forced)(sc.breakOut)
    new JArray(forcedArray) {
      override def forced = this
    }
  }

  override def equals(o: Any): Boolean = {
    o match {
      case that: JArray =>
        this.elems == that.elems
      case _ =>
        false
    }
  }

  def jsonType = JArray
}

object JArray extends scala.runtime.AbstractFunction1[Seq[JValue], JArray] with JsonType {
  val empty: JArray = new JArray(Vector.empty) { // Vector because JsonReader is guaranteed to return JArrays which contain Vectors.
    override def forced = this
  }
  val canonicalEmpty = empty
  override final val toString = "array"
  implicit object Concrete extends Json[JArray] {
    val jsonTypes = Set[JsonType](JArray)
  }
}

/** A JSON object, implemented as a thin wrapper around a map from `String` to [[com.rojoma.json.v3.ast.JValue]].
  * In many ways this can be treated as a `Map`, but it is in fact not one. */
case class JObject(val fields: sc.Map[String, JValue]) extends Iterable[(String, JValue)] with PartialFunction[String, JValue] with JCompound {
  override def size = fields.size
  def contains(s: String) = fields.contains(s)
  def apply(key: String) = fields(key)
  def get(key: String) = fields.get(key)
  def getOrElse[B1 >: JValue](key: String, default: => B1): B1 = fields.getOrElse(key, default)
  def isDefinedAt(key: String) = fields.isDefinedAt(key)
  def iterator = fields.iterator
  def keys = fields.keys
  def keysIterator = fields.keysIterator
  def keySet = fields.keySet
  def values = fields.values
  def valuesIterator = fields.valuesIterator
  def mapValues[C](f: JValue => C): sc.Map[String, C] = fields.mapValues(f)
  override def toSeq = fields.toSeq
  override def toMap[T, U] (implicit ev: <:<[(String, JValue), (T, U)]): Map[T, U] = fields.toMap

  def forced: JObject = {
    // would be nice to freeze this into an actual immutable Map in
    // order to preserve ordering and yet be actually unchangable, but
    // instead we'll just trust that the Bad People who are relying on
    // the ordering of fields in their JSON objects are not *so* bad
    // that they'll downcast an sc.Map to an scm.Map.  Unchangability
    // of the result isn't part of "forced"'s contract anyway; merely
    // full evaluation.
    val forcedMap = new sc.mutable.LinkedHashMap[String, JValue]
    for((k, v) <- fields) forcedMap += k -> v.forced
    new JObject(forcedMap) {
      override def forced = this
    }
  }

  def jsonType = JObject
}

object JObject extends scala.runtime.AbstractFunction1[sc.Map[String, JValue], JObject]  with JsonType {
  val empty: JObject = new JObject(Map.empty) { // _Not_ LinkedHashMap because all JsonReader guarantees is ordering of elements, which this satisfies.
    override def forced = this
  }
  val canonicalEmpty = empty
  override final val toString = "object"
  implicit object Concrete extends Json[JObject] {
    val jsonTypes = Set[JsonType](JObject)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy