alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Play Framework/Scala example source code file (JsValue.scala)

This example Play Framework source code file (JsValue.scala) is included in my "Source Code Warehouse" project. The intent of this project is to help you more easily find Play Framework (and Scala) source code examples by using tags.

All credit for the original source code belongs to Play Framework; I'm just trying to make examples easier to find. (For my Scala work, see my Scala examples and tutorials.)

Play Framework tags/keywords

api, boolean, collection, deserializercontext, jsarray, jsobject, jsundefined, jsvalue, library, play, play framework, seq, some, string, validation, vector

The JsValue.scala Play Framework example source code

/*
 * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
 */
package play.api.libs.json

import scala.annotation.tailrec
import scala.collection._
import scala.collection.mutable.ListBuffer

import com.fasterxml.jackson.core.{ JsonGenerator, JsonToken, JsonParser }
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.`type`.TypeFactory
import com.fasterxml.jackson.databind.deser.Deserializers
import com.fasterxml.jackson.databind.ser.Serializers

import play.api.data.validation.ValidationError

case class JsResultException(errors: Seq[(JsPath, Seq[ValidationError])]) extends RuntimeException("JsResultException(errors:%s)".format(errors))

/**
 * Generic json value
 */
sealed trait JsValue {

  /**
   * Return the property corresponding to the fieldName, supposing we have a JsObject.
   *
   * @param fieldName the name of the property to lookup
   * @return the resulting JsValue. If the current node is not a JsObject or doesn't have the property, a JsUndefined will be returned.
   */
  def \(fieldName: String): JsValue = JsUndefined("'" + fieldName + "'" + " is undefined on object: " + this)

  /**
   * Return the element at a given index, supposing we have a JsArray.
   *
   * @param idx the index to lookup
   * @return the resulting JsValue. If the current node is not a JsArray or the index is out of bounds, a JsUndefined will be returned.
   */
  def apply(idx: Int): JsValue = JsUndefined(this.toString + " is not an array")

  /**
   * Lookup for fieldName in the current object and all descendants.
   *
   * @return the list of matching nodes
   */
  def \\(fieldName: String): Seq[JsValue] = Nil

  /**
   * Tries to convert the node into a T. An implicit Reads[T] must be defined.
   * Any error is mapped to None
   *
   * @return Some[T] if it succeeds, None if it fails.
   */
  def asOpt[T](implicit fjs: Reads[T]): Option[T] = fjs.reads(this).fold(
    invalid = _ => None,
    valid = v => Some(v)
  ).filter {
      case JsUndefined() => false
      case _ => true
    }

  /**
   * Tries to convert the node into a T, throwing an exception if it can't. An implicit Reads[T] must be defined.
   */
  def as[T](implicit fjs: Reads[T]): T = fjs.reads(this).fold(
    valid = identity,
    invalid = e => throw new JsResultException(e)
  )

  /**
   * Tries to convert the node into a JsResult[T] (Success or Error). An implicit Reads[T] must be defined.
   */
  def validate[T](implicit rds: Reads[T]): JsResult[T] = rds.reads(this)

  /**
   * Transforms a JsValue into another JsValue using provided Json transformer Reads[JsValue]
   */
  def transform[A <: JsValue](rds: Reads[A]): JsResult[A] = rds.reads(this)

  override def toString = Json.stringify(this)

  /**
   * Prune the Json AST according to the provided JsPath
   */
  //def prune(path: JsPath): JsValue = path.prune(this)

}

/**
 * Represents a Json null value.
 */
case object JsNull extends JsValue

/**
 * Represent a missing Json value.
 */
class JsUndefined(err: => String) extends JsValue {
  def error = err
  override def toString = "JsUndefined(" + err + ")"
}

object JsUndefined {
  def apply(err: => String) = new JsUndefined(err)
  def unapply(o: Object): Boolean = o.isInstanceOf[JsUndefined]
}

/**
 * Represent a Json boolean value.
 */
case class JsBoolean(value: Boolean) extends JsValue

/**
 * 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: Seq[JsValue] = List()) extends JsValue {

  /**
   * Access a value of this array.
   *
   * @param index Element index.
   */
  override def apply(index: Int): JsValue = {
    value.lift(index).getOrElse(JsUndefined("Array index out of bounds in " + this))
  }

  /**
   * Lookup for fieldName in the current object and all descendants.
   *
   * @return the list of matching nodes
   */
  override def \\(fieldName: String): Seq[JsValue] = value.flatMap(_ \\ fieldName)

  /**
   * 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)

}

/**
 * Represent a Json object value.
 */
case class JsObject(fields: Seq[(String, JsValue)]) extends JsValue {

  lazy val value: Map[String, JsValue] = fields.toMap

  /**
   * Return the property corresponding to the fieldName, supposing we have a JsObject.
   *
   * @param fieldName the name of the property to lookup
   * @return the resulting JsValue. If the current node is not a JsObject or doesn't have the property, a JsUndefined will be returned.
   */
  override def \(fieldName: String): JsValue = value.get(fieldName).getOrElse(super.\(fieldName))

  /**
   * Lookup for fieldName in the current object and all descendants.
   *
   * @return the list of matching nodes
   */
  override def \\(fieldName: String): Seq[JsValue] = {
    value.foldLeft(Seq[JsValue]())((o, pair) => pair match {
      case (key, value) if key == fieldName => o ++ (value +: (value \\ fieldName))
      case (_, value) => o ++ (value \\ fieldName)
    })
  }

  /**
   * Return all keys
   */
  def keys: Set[String] = fields.map(_._1).toSet

  /**
   * Return all values
   */
  def values: Set[JsValue] = fields.map(_._2).toSet

  def fieldSet: Set[(String, JsValue)] = fields.toSet

  /**
   * Merge this object with an other one. Values from other override value of the current object.
   */
  def ++(other: JsObject): JsObject =
    JsObject(fields.filterNot(field => other.keys(field._1)) ++ other.fields)

  /**
   * removes one field from JsObject
   */
  def -(otherField: String): JsObject =
    JsObject(fields.filterNot(_._1 == otherField))

  /**
   * adds one field from JsObject
   */
  def +(otherField: (String, JsValue)): JsObject =
    JsObject(fields :+ otherField)

  /**
   * merges everything in depth and doesn't stop at first level as ++
   * TODO : improve because coding is nasty there
   */
  def deepMerge(other: JsObject): JsObject = {
    def step(fields: Vector[(String, JsValue)], others: Vector[(String, JsValue)]): Seq[(String, JsValue)] = {
      others match {
        case Vector() => fields
        case Vector(sv) =>
          var found = false
          val newFields = fields match {
            case Vector() => Vector(sv)
            case _ => fields.foldLeft(Vector[(String, JsValue)]()) { (acc, field) =>
              field match {
                case (key, obj: JsObject) if (key == sv._1) =>
                  found = true
                  acc :+ key -> {
                    sv._2 match {
                      case o @ JsObject(_) => obj.deepMerge(o)
                      case js => js
                    }
                  }
                case (key, value) if (key == sv._1) =>
                  found = true
                  acc :+ key -> sv._2
                case (key, value) => acc :+ key -> value
              }
            }
          }

          if (!found) fields :+ sv
          else newFields

        case head +: tail =>
          var found = false
          val headFields = fields match {
            case Vector() => Vector(head)
            case _ => fields.foldLeft(Vector[(String, JsValue)]()) { (acc, field) =>
              field match {
                case (key, obj: JsObject) if (key == head._1) =>
                  found = true
                  acc :+ key -> {
                    head._2 match {
                      case o @ JsObject(_) => obj.deepMerge(o)
                      case js => js
                    }
                  }
                case (key, value) if (key == head._1) =>
                  found = true
                  acc :+ key -> head._2
                case (key, value) => acc :+ key -> value
              }
            }
          }

          if (!found) step(fields :+ head, tail)
          else step(headFields, tail)

      }
    }

    JsObject(step(fields.toVector, other.fields.toVector))
  }

  override def equals(other: Any): Boolean =
    other match {

      case that: JsObject =>
        (that canEqual this) &&
          fieldSet == that.fieldSet

      case _ => false
    }

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

  override def hashCode: Int = fieldSet.hashCode()

}

// -- Serializers.

private[json] class JsValueSerializer extends JsonSerializer[JsValue] {

  override def serialize(value: JsValue, json: JsonGenerator, provider: SerializerProvider) {
    value match {
      case JsNumber(v) => json.writeNumber(v.bigDecimal)
      case JsString(v) => json.writeString(v)
      case JsBoolean(v) => json.writeBoolean(v)
      case JsArray(elements) => {
        json.writeStartArray()
        elements.foreach { t =>
          serialize(t, json, provider)
        }
        json.writeEndArray()
      }
      case JsObject(values) => {
        json.writeStartObject()
        values.foreach { t =>
          json.writeFieldName(t._1)
          serialize(t._2, json, provider)
        }
        json.writeEndObject()
      }
      case JsNull => json.writeNull()
      case JsUndefined() => {
        json.writeNull()
      }
    }
  }
}

private[json] sealed trait DeserializerContext {
  def addValue(value: JsValue): DeserializerContext
}

private[json] case class ReadingList(content: ListBuffer[JsValue]) extends DeserializerContext {
  override def addValue(value: JsValue): DeserializerContext = {
    ReadingList(content += value)
  }
}

// Context for reading an Object
private[json] case class KeyRead(content: ListBuffer[(String, JsValue)], fieldName: String) extends DeserializerContext {
  def addValue(value: JsValue): DeserializerContext = ReadingMap(content += (fieldName -> value))
}

// Context for reading one item of an Object (we already red fieldName)
private[json] case class ReadingMap(content: ListBuffer[(String, JsValue)]) extends DeserializerContext {

  def setField(fieldName: String) = KeyRead(content, fieldName)
  def addValue(value: JsValue): DeserializerContext = throw new Exception("Cannot add a value on an object without a key, malformed JSON object!")

}

private[json] class JsValueDeserializer(factory: TypeFactory, klass: Class[_]) extends JsonDeserializer[Object] {

  override def isCachable: Boolean = true

  override def deserialize(jp: JsonParser, ctxt: DeserializationContext): JsValue = {
    val value = deserialize(jp, ctxt, List())

    if (!klass.isAssignableFrom(value.getClass)) {
      throw ctxt.mappingException(klass)
    }
    value
  }

  @tailrec
  final def deserialize(jp: JsonParser, ctxt: DeserializationContext, parserContext: List[DeserializerContext]): JsValue = {
    if (jp.getCurrentToken == null) {
      jp.nextToken()
    }

    val (maybeValue, nextContext) = (jp.getCurrentToken, parserContext) match {

      case (JsonToken.VALUE_NUMBER_INT | JsonToken.VALUE_NUMBER_FLOAT, c) => (Some(JsNumber(jp.getDecimalValue)), c)

      case (JsonToken.VALUE_STRING, c) => (Some(JsString(jp.getText)), c)

      case (JsonToken.VALUE_TRUE, c) => (Some(JsBoolean(true)), c)

      case (JsonToken.VALUE_FALSE, c) => (Some(JsBoolean(false)), c)

      case (JsonToken.VALUE_NULL, c) => (Some(JsNull), c)

      case (JsonToken.START_ARRAY, c) => (None, (ReadingList(ListBuffer())) +: c)

      case (JsonToken.END_ARRAY, ReadingList(content) :: stack) => (Some(JsArray(content)), stack)

      case (JsonToken.END_ARRAY, _) => throw new RuntimeException("We should have been reading list, something got wrong")

      case (JsonToken.START_OBJECT, c) => (None, ReadingMap(ListBuffer()) +: c)

      case (JsonToken.FIELD_NAME, (c: ReadingMap) :: stack) => (None, c.setField(jp.getCurrentName) +: stack)

      case (JsonToken.FIELD_NAME, _) => throw new RuntimeException("We should be reading map, something got wrong")

      case (JsonToken.END_OBJECT, ReadingMap(content) :: stack) => (Some(JsObject(content)), stack)

      case (JsonToken.END_OBJECT, _) => throw new RuntimeException("We should have been reading an object, something got wrong")

      case (JsonToken.NOT_AVAILABLE, _) => throw new RuntimeException("We should have been reading an object, something got wrong")

      case (JsonToken.VALUE_EMBEDDED_OBJECT, _) => throw new RuntimeException("We should have been reading an object, something got wrong")
    }

    // Read ahead
    jp.nextToken()

    maybeValue match {
      case Some(v) if nextContext.isEmpty =>
        // done, no more tokens and got a value!
        // note: jp.getCurrentToken == null happens when using treeToValue (we're not parsing tokens)
        v

      case maybeValue =>
        val toPass = maybeValue.map { v =>
          val previous :: stack = nextContext
          (previous.addValue(v)) +: stack
        }.getOrElse(nextContext)

        deserialize(jp, ctxt, toPass)

    }

  }

  // This is used when the root object is null, ie when deserialising "null"
  override def getNullValue = JsNull
}

private[json] class PlayDeserializers(classLoader: ClassLoader) extends Deserializers.Base {
  override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig, beanDesc: BeanDescription) = {
    val klass = javaType.getRawClass
    if (classOf[JsValue].isAssignableFrom(klass) || klass == JsNull.getClass) {
      new JsValueDeserializer(config.getTypeFactory, klass)
    } else null
  }

}

private[json] class PlaySerializers extends Serializers.Base {
  override def findSerializer(config: SerializationConfig, javaType: JavaType, beanDesc: BeanDescription) = {
    val ser: Object = if (classOf[JsValue].isAssignableFrom(beanDesc.getBeanClass)) {
      new JsValueSerializer
    } else {
      null
    }
    ser.asInstanceOf[JsonSerializer[Object]]
  }
}

private[play] object JacksonJson {

  import com.fasterxml.jackson.core.Version
  import com.fasterxml.jackson.databind.module.SimpleModule
  import com.fasterxml.jackson.databind.Module.SetupContext

  private[this] val classLoader = Thread.currentThread().getContextClassLoader

  def createMapper(): ObjectMapper = (new ObjectMapper).registerModule(module)

  private[json] val mapper = createMapper()

  private[json] object module extends SimpleModule("PlayJson", Version.unknownVersion()) {
    override def setupModule(context: SetupContext) {
      context.addDeserializers(new PlayDeserializers(classLoader))
      context.addSerializers(new PlaySerializers)
    }
  }

  private[this] lazy val jsonFactory = new com.fasterxml.jackson.core.JsonFactory(mapper)

  private[this] def stringJsonGenerator(out: java.io.StringWriter) = jsonFactory.createGenerator(out)

  private[this] def jsonParser(c: String): JsonParser = jsonFactory.createParser(c)

  private[this] def jsonParser(data: Array[Byte]): JsonParser = jsonFactory.createParser(data)

  def parseJsValue(data: Array[Byte]): JsValue = {
    mapper.readValue(jsonParser(data), classOf[JsValue])
  }

  def parseJsValue(input: String): JsValue = {
    mapper.readValue(jsonParser(input), classOf[JsValue])
  }

  def generateFromJsValue(jsValue: JsValue, escapeNonASCII: Boolean = false): String = {
    val sw = new java.io.StringWriter
    val gen = stringJsonGenerator(sw)
    if (escapeNonASCII) {
      gen.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII)
    }
    mapper.writeValue(gen, jsValue)
    sw.flush()
    sw.getBuffer.toString
  }

  def prettyPrint(jsValue: JsValue): String = {
    val sw = new java.io.StringWriter
    val gen = stringJsonGenerator(sw).setPrettyPrinter(
      new com.fasterxml.jackson.core.util.DefaultPrettyPrinter()
    )
    mapper.writerWithDefaultPrettyPrinter().writeValue(gen, jsValue)
    sw.flush()
    sw.getBuffer.toString
  }

  def jsValueToJsonNode(jsValue: JsValue): JsonNode = {
    mapper.valueToTree(jsValue)
  }

  def jsonNodeToJsValue(jsonNode: JsonNode): JsValue = {
    mapper.treeToValue(jsonNode, classOf[JsValue])
  }

}

Other Play Framework source code examples

Here is a short list of links related to this Play Framework JsValue.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.