src/Pure/General/json.scala
author wenzelm
Thu, 09 Feb 2017 15:40:34 +0100
changeset 65009 eda9366bbfac
parent 64878 e9208a9301c0
child 66922 5a476a87a535
permissions -rw-r--r--
remote database access via ssh port forwarding;

/*  Title:      Pure/General/json.scala
    Author:     Makarius

Support for JSON parsing.
*/

package isabelle


import scala.util.parsing.json.{JSONObject, JSONArray}

object JSON
{
  type T = Any
  type S = String


  /* standard format */

  def parse(s: S): T = Format.unapply(s) getOrElse error("Malformed JSON")

  object Format
  {
    def unapply(s: S): Option[T] =
      try { scala.util.parsing.json.JSON.parseFull(s) }
      catch { case ERROR(_) => None }

    def apply(json: T): S =
    {
      val result = new StringBuilder

      def string(s: String)
      {
        result += '"'
        result ++=
          s.iterator.map {
            case '"'  => "\\\""
            case '\\' => "\\\\"
            case '\b' => "\\b"
            case '\f' => "\\f"
            case '\n' => "\\n"
            case '\r' => "\\r"
            case '\t' => "\\t"
            case c =>
              if (c <= '\u001f' || c >= '\u007f' && c <= '\u009f') "\\u%04x".format(c.toInt)
              else c
          }.mkString
        result += '"'
      }

      def array(list: List[T])
      {
        result += '['
        Library.separate(None, list.map(Some(_))).foreach({
          case None => result += ','
          case Some(x) => json_format(x)
        })
        result += ']'
      }

      def object_(obj: Map[String, T])
      {
        result += '{'
        Library.separate(None, obj.toList.map(Some(_))).foreach({
          case None => result += ','
          case Some((x, y)) =>
            string(x)
            result += ':'
            json_format(y)
        })
        result += '}'
      }

      def json_format(x: T)
      {
        x match {
          case null => result ++= "null"
          case _: Int | _: Long | _: Boolean => result ++= x.toString
          case n: Double =>
            val i = n.toLong
            result ++= (if (i.toDouble == n) i.toString else n.toString)
          case s: String => string(s)
          case JSONObject(obj) => object_(obj)
          case obj: Map[_, _] if obj.keySet.forall(_.isInstanceOf[String]) =>
            object_(obj.asInstanceOf[Map[String, T]])
          case JSONArray(list) => array(list)
          case list: List[T] => array(list)
          case _ => error("Bad JSON value: " + x.toString)
        }
      }

      json_format(json)
      result.toString
    }
  }


  /* numbers */

  object Number
  {
    object Double {
      def unapply(json: T): Option[scala.Double] =
        json match {
          case x: scala.Double => Some(x)
          case x: scala.Long => Some(x.toDouble)
          case x: scala.Int => Some(x.toDouble)
          case _ => None
        }
    }

    object Long {
      def unapply(json: T): Option[scala.Long] =
        json match {
          case x: scala.Double if x.toLong.toDouble == x => Some(x.toLong)
          case x: scala.Long => Some(x)
          case x: scala.Int => Some(x.toLong)
          case _ => None
        }
    }

    object Int {
      def unapply(json: T): Option[scala.Int] =
        json match {
          case x: scala.Double if x.toInt.toDouble == x => Some(x.toInt)
          case x: scala.Long if x.toInt.toLong == x => Some(x.toInt)
          case x: scala.Int => Some(x)
          case _ => None
        }
    }
  }


  /* object values */

  def optional(entry: (String, Option[T])): Map[String, T] =
    entry match {
      case (name, Some(x)) => Map(name -> x)
      case (_, None) => Map.empty
    }

  def value(obj: T, name: String): Option[T] =
    obj match {
      case m: Map[_, _] if m.keySet.forall(_.isInstanceOf[String]) =>
        m.asInstanceOf[Map[String, T]].get(name)
      case _ => None
    }

  def value[A](obj: T, name: String, unapply: T => Option[A]): Option[A] =
    for {
      json <- value(obj, name)
      x <- unapply(json)
    } yield x

  def array(obj: T, name: String): Option[List[T]] =
    value(obj, name) match {
      case Some(a: List[T]) => Some(a)
      case _ => None
    }

  def array[A](obj: T, name: String, unapply: T => Option[A]): Option[List[A]] =
    for {
      a0 <- array(obj, name)
      a = a0.map(unapply(_))
      if a.forall(_.isDefined)
    } yield a.map(_.get)

  def string(obj: T, name: String): Option[String] =
    value(obj, name) match {
      case Some(x: String) => Some(x)
      case _ => None
    }

  def double(obj: T, name: String): Option[Double] =
    value(obj, name, Number.Double.unapply)

  def long(obj: T, name: String): Option[Long] =
    value(obj, name, Number.Long.unapply)

  def int(obj: T, name: String): Option[Int] =
    value(obj, name, Number.Int.unapply)

  def bool(obj: T, name: String): Option[Boolean] =
    value(obj, name) match {
      case Some(x: Boolean) => Some(x)
      case _ => None
    }
}