|
Play Framework/Scala example source code file (JsValue.scala)
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 examplesHere 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 |
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.