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

com.identityblitz.json.JVal.scala Maven / Gradle / Ivy

The newest version!
package com.identityblitz.json

import org.codehaus.jackson.map._
import org.codehaus.jackson.`type`.JavaType
import org.codehaus.jackson.{JsonToken, JsonParser, Version, JsonGenerator}
import org.codehaus.jackson.map.annotate.JsonCachable
import org.codehaus.jackson.map.module.SimpleModule
import org.codehaus.jackson.map.Module.SetupContext
import java.io.{InputStream, StringWriter}
import org.codehaus.jackson.map.`type`.TypeFactory
import scala.collection.mutable
import scala.collection.generic.CanBuildFrom
import scala.language.implicitConversions


/**
 *
 */
trait JVal {

  /**
   * Extract a value corresponding to filed name passed, from this object. It works only in JSON objects.
   * @param filed - field name
   * @return - value - if found;
   *           JUndef - if not found.
   */
  def \(filed: String): JVal = JUndef

  /**
   * Serializes this object to string
   * @return a string in JSON format
   */
  def toJson: String = JacksonBridge.jVal2JsonString(this)

  /**
   * Unmarshal a JVal object into an object of type T.
   * @param reader - reader being used to unmarshal
   * @tparam T - type to unmarshal in
   * @return - an unmarshalled object
   */
  def as[T](implicit reader: JReader[T]) = reader.read(this).fold[T](e => throw new IllegalStateException(e.toString()))(v => v)

  /**
   * Unmarshal a JVal object into an Option containing object of type T.
   * @param reader - reader being used to unmarshal
   * @tparam T - type to unmarshal in
   * @return - an Option of unmarshalled object or None if any errors occurred
   */
  def asOpt[T](implicit reader: JReader[T]): Option[T] = reader.read(this).fold[Option[T]](_ => None)(v => Option(v))

  def read[T](implicit reader: JReader[T]): JResult[T] = reader.read(this)

}

object JVal {

  /**
   * Parses input string containing a JSON object.
   * @param str - string containing a JSON object.
   * @return - a JSON object
   */
  def parse(str: String): JVal = JacksonBridge.jsonString2JVal(str)

  /**
   * Parses input stream containing a JSON object.
   * @param in - input stream
   * @return a JSON object
   */
  def parse(in: InputStream): JVal = JacksonBridge.jsonString2JVal(in)

}

case object JUndef extends JVal {
  override def toString: String = "JUndef"
}

case object JNull extends JVal {
  override def toString: String = "JNull"
}

case class JNum(private val value: BigDecimal) extends JVal {
  override def toString: String = "JNum(" + value + ")"
}

object JNum {
  implicit def bigDecimalConverter(j: JNum): BigDecimal = j.value
}

case class JStr(private val value: String) extends JVal {
  override def toString: String = "JStr(" + value + ")"
}

object JStr {
  implicit def stringConverter(j: JStr): String = j.value
}

case class JBool(private val value: Boolean) extends JVal {
  override def toString: String = "JBool(" + value + ")"
}

object JBool {
  implicit def booleanConverter(j: JBool): Boolean = j.value
}

case class JArr(private val v: Array[JVal]) extends JVal {

  def this(b: mutable.Buffer[JVal]) = this(b.toArray)

  private val value: Array[JVal] = v.clone()

  def apply[B <: JVal](idx: Int): B = value(idx).asInstanceOf[B]

  def +:[T](element: T)(implicit writer: JWriter[T]) = JArr.prepend(this, element)
  def :+[T](element: T)(implicit writer: JWriter[T]) = JArr.append(this, element)

  def foreach(f: (JVal) => Unit): Unit = value.foreach(f)

  def map[B](f: (JVal) => B)(implicit bf: CanBuildFrom[Array[JVal], B, Array[B]]): Array[B] = value.map(f)(bf)

  def forall(f: (JVal) => Boolean): Boolean = value.forall(f)

  override def toString = v.mkString("JArr(", ", ", ")")

}

object JArr {

  def prepend[T](arr: JArr, element: T)(implicit writer: JWriter[T]): JArr = JArr((writer.write(element) +: arr.value.toSeq).toArray)

  def append[T](arr: JArr, element: T)(implicit writer: JWriter[T]): JArr = JArr((arr.value.toSeq :+ writer.write(element)).toArray)

}

case class JObj(private val v: Seq[(String, JVal)]) extends JVal {

  private[json] val value = v.toMap

  def apply(name: String):JVal = value.get(name).getOrElse(JUndef)

  def +[T](name: String, value: T)(implicit writer: JWriter[T]): JObj = JObj.add(this, name, value)
  def +[T](v: (String, T))(implicit writer: JWriter[T]): JObj = this + (v._1, v._2)

  def +![T](name: String, value: T)(implicit writer: JWriter[T]): JObj = JObj.addOrReplace(this, name, value)
  def +![T](v: (String, T))(implicit writer: JWriter[T]): JObj = this +! (v._1, v._2)

  def ++!(that: JObj): JObj = JObj.addOrReplace(this, that)

  def fields: Set[String] = value.keySet

  def isEmpty: Boolean = value.isEmpty

  def foreach(f: ((String, JVal)) => Unit): Unit = value.foreach(f)

  def map[B, That](f: ((String, JVal)) => B)(implicit bf: CanBuildFrom[Map[String, JVal], B, That]): That = value.map(f)(bf)

  def forall(f: ((String, JVal)) => Boolean): Boolean = value.forall(f)

  override def \(field: String) = value.get(field).getOrElse(JUndef)

  override def toString: String = value.map(e => "\"" + e._1 + "\":" + e._2).mkString("JObj(", ",", ")")
}

object JObj {

  def apply(): JObj = new JObj(Seq())
  def apply(name: String, value: JVal): JObj = new JObj(Seq((name, value)))
  def apply(v: (String, JVal)): JObj = new JObj(Seq(v))

  @inline
  private def add[T](obj: JObj, fieldName: String, fieldValue: T)(implicit writer: JWriter[T]): JObj = if (obj.value.contains(fieldName)) obj else JObj(obj.value.toSeq :+ ((fieldName, Json.toJson(fieldValue))))

  @inline
  private def addOrReplace[T](obj: JObj, fieldName: String, fieldValue: T)(implicit writer: JWriter[T]): JObj = JObj(obj.value.toSeq :+ ((fieldName, Json.toJson(fieldValue))))

  @inline
  private def addOrReplace(obj: JObj, that: JObj): JObj = JObj(obj.value.toSeq ++ that.value.toSeq)

}

@JsonCachable
private[json] class JSerializer extends JsonSerializer[JVal]{
  def serialize(bean: JVal, generator: JsonGenerator, provider: SerializerProvider) {
    bean match {
      case JNull => generator.writeNull()
      case JNum(v) => generator.writeNumber(v.bigDecimal)
      case JStr(v) => generator.writeString(v)
      case JBool(v) => generator.writeBoolean(v)
      case JArr(v) => {
        generator.writeStartArray()
        v.foreach(serialize(_, generator, provider))
        generator.writeEndArray()
      }
      case o: JObj => {
        generator.writeStartObject()
        o.value.foreach(e => {
          e._2 match {
            case JUndef =>
            case v => {
              generator.writeFieldName(e._1)
              serialize(v, generator, provider)
            }
          }
        })
        generator.writeEndObject()
      }
    }
  }
}

object JElemType extends Enumeration {
  type JElemType = Value
  val SIMPLE, OBJECT, ARRAY, FIELD = Value
}
import JElemType._

trait JVisitor {
  def visit(l: JElemType, s: AnyRef)
  def visit(l: JElemType)(f: AnyRef=>JVal)
  def visit(e: JVal)
  def produce: JVal
}

class JValVisitor extends JVisitor {

  var level = SIMPLE
  val levels = mutable.Stack[(JElemType, AnyRef)]()
  var cur: AnyRef = null
  var finalElem: JVal = _

  @inline
  private def popLevel = {
    val old = cur
    levels.pop() match {
      case (l, c) => level = l; cur = c
    }
    old
  }

  @inline
  private def fieldEnd(v: JVal) {
    val fieldName = popLevel.asInstanceOf[String]
    if(level != OBJECT)
      throw new IllegalStateException("Mailformed JSON. After finishing a filed found level = " + level + " on the stack")
    cur.asInstanceOf[mutable.ListBuffer[(String, JVal)]] += ((fieldName, v))
  }

  def visit(l: JElemType, s: AnyRef) {
    if(level != SIMPLE) {
      levels.push((level, cur))
    }
    cur = s
    level = l
  }

  def visit(l: JElemType)(f: AnyRef=>JVal) {
    if(level != l) {
      throw new IllegalStateException("Malformed JSON. Called for level '" + l + "' but current level is " + level)
    }
    val value = f(cur)
    if(levels.isEmpty) {
      finalElem = value
    }
    else {
      popLevel
      level match {
        case ARRAY => cur.asInstanceOf[mutable.ListBuffer[JVal]] += value
        case FIELD => fieldEnd(value)
        case _ @ lvl => throw new IllegalStateException("Malformed JSON. After ending level '" + lvl + "' on stack found level = " + lvl)
      }
    }
  }

  def visit(e: JVal) {
    if(level == SIMPLE) {
      finalElem = e
    }
    else {
      level match {
        case ARRAY => cur.asInstanceOf[mutable.ListBuffer[JVal]] += e
        case FIELD => fieldEnd(e)
        case _ @ lvl => throw new IllegalStateException("Malformed JSON. After JNum on stack found level = " + lvl)
      }
    }
  }

  def produce: JVal = finalElem
}

@JsonCachable
private[json] class JDeserializer(factory: TypeFactory, klass: Class[_]) extends JsonDeserializer[Object] {
  def deserialize(jp: JsonParser, ctx: DeserializationContext): JVal = {

    val visitor: JVisitor = new JValVisitor

    if(!jp.hasCurrentToken)
      jp.nextToken()
    var tkn = jp.getCurrentToken

    while(tkn != null) {
      tkn match {
        case JsonToken.NOT_AVAILABLE => throw new IllegalStateException("A JSON stream has finished unexpectedly")
        case JsonToken.VALUE_EMBEDDED_OBJECT => throw new IllegalStateException("An embedded object found.")
        case _ => accept(tkn, jp, visitor)
      }
      tkn = jp.nextToken()
    }

    val value = visitor.produce
    if (!klass.isAssignableFrom(value.getClass)) {
      throw ctx.mappingException(klass)
    }
    value
  }

  @inline
  def accept(tkn: JsonToken, jp: JsonParser, visitor: JVisitor) {
    tkn match {
      case JsonToken.START_OBJECT => visitor.visit(OBJECT, new mutable.ListBuffer[(String, JVal)]())
      case JsonToken.FIELD_NAME => visitor.visit(FIELD, jp.getCurrentName)
      case JsonToken.END_OBJECT => visitor.visit(OBJECT)(s => new JObj(s.asInstanceOf[mutable.ListBuffer[(String, JVal)]]))
      case JsonToken.START_ARRAY => visitor.visit(ARRAY, new mutable.ListBuffer[JVal]())
      case JsonToken.END_ARRAY => visitor.visit(ARRAY)(s => new JArr(s.asInstanceOf[mutable.ListBuffer[JVal]]))
      case JsonToken.VALUE_NUMBER_INT => visitor.visit(JNum(jp.getDecimalValue))
      case JsonToken.VALUE_NUMBER_FLOAT => visitor.visit(JNum(jp.getDecimalValue))
      case JsonToken.VALUE_NULL => visitor.visit(JNull)
      case JsonToken.VALUE_FALSE => visitor.visit(JBool(jp.getBooleanValue))
      case JsonToken.VALUE_TRUE => visitor.visit(JBool(jp.getBooleanValue))
      case JsonToken.VALUE_STRING => visitor.visit(JStr(jp.getText))
      case _ => throw new IllegalStateException("Found unknown token " + tkn)
    }
  }

}

private[json] class JSerializers extends Serializers.Base {
  override def findSerializer(config: SerializationConfig, javaType: JavaType, beanDesc: BeanDescription, property: BeanProperty) =
    if(classOf[JVal].isAssignableFrom(beanDesc.getBeanClass)) {
      new JSerializer
    }
    else {
      null
    }.asInstanceOf[JsonSerializer[Object]]
}

private[json] class JDeserializers extends Deserializers.Base {
  override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig,
                                    provider: DeserializerProvider, beanDesc: BeanDescription,
                                    property: BeanProperty) = {
    val klass = javaType.getRawClass
    if (classOf[JVal].isAssignableFrom(klass)) {
      new JDeserializer(config.getTypeFactory, klass)
    } else null
  }

}

private[json] object JacksonBridge {

  val mapper = new ObjectMapper

  object module extends SimpleModule("BlitzModule", new Version(1, 0, 0, null)) {
    override def setupModule(context: SetupContext) {
      context.addSerializers(new JSerializers)
      context.addDeserializers(new JDeserializers)
    }
  }
  mapper.registerModule(module)

  private[this] val factory = mapper.getJsonFactory

  private[this] def generator(output: StringWriter) = factory.createJsonGenerator(output)

  private[this] def jsonParser(s: String) = factory.createJsonParser(s)

  private[this] def jsonParser(in: InputStream) = factory.createJsonParser(in)

  private[json] def jVal2JsonString(value: JVal): String = {
    val writer = new StringWriter
    mapper.writeValue(generator(writer), value)
    writer.flush()
    writer.toString
  }

  private[json] def jsonString2JVal(strJson: String): JVal = {
    mapper.readValue(jsonParser(strJson), classOf[JVal])
  }

  private[json] def jsonString2JVal(jsonStream: InputStream): JVal = {
    mapper.readValue(jsonParser(jsonStream), classOf[JVal])
  }

}








© 2015 - 2025 Weber Informatics LLC | Privacy Policy