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

wvlet.airframe.json.JSON.scala Maven / Gradle / Ivy

There is a newer version: 24.12.2
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wvlet.airframe.json

import wvlet.log.LogSupport

/**
  *
  */
object JSON extends LogSupport {

  /**
    * Parse JSON object or array
    */
  def parse(s: String): JSONValue = {
    parse(JSONSource.fromString(s))
  }

  /**
    * Parse JSON object or array
    */
  def parse(s: Array[Byte]): JSONValue = {
    parse(JSONSource.fromBytes(s))
  }

  /**
    * Parse JSON object or array
    */
  def parse(s: JSONSource): JSONValue = {
    val b = new JSONValueBuilder().singleContext(s, 0)
    JSONScanner.scan(s, b)
    val j = b.result
    j
  }

  /**
    * Parse any json values including null
    */
  def parseAny(s: String): JSONValue = {
    parseAny(JSONSource.fromString(s))
  }

  /**
    * Parse any json values including null
    */
  def parseAny(s: Array[Byte]): JSONValue = {
    parseAny(JSONSource.fromBytes(s))
  }

  /**
    * Parse any json values including null
    */
  def parseAny(s: JSONSource): JSONValue = {
    val b = new JSONValueBuilder().singleContext(s, 0)
    JSONScanner.scanAny(s, b)
  }

  sealed trait JSONValue {
    override def toString = toJSON
    def toJSON: String
  }

  final case object JSONNull extends JSONValue {
    override def toJSON: String = "null"
  }

  final case class JSONBoolean(val v: Boolean) extends JSONValue {
    override def toJSON: String = if (v) "true" else "false"
  }

  val JSONTrue  = JSONBoolean(true)
  val JSONFalse = JSONBoolean(false)

  trait JSONNumber extends JSONValue
  final case class JSONDouble(v: Double) extends JSONNumber {
    override def toJSON: String = v.toString
  }
  final case class JSONLong(v: Long) extends JSONNumber {
    override def toJSON: String = v.toString
  }
  final case class JSONString(v: String) extends JSONValue {
    override def toString = v
    override def toJSON: String = {
      val s = new StringBuilder(v.length + 2)
      s.append("\"")
      s.append(quoteJSONString(v))
      s.append("\"")
      s.result()
    }
  }

  final case class JSONObject(v: Seq[(String, JSONValue)]) extends JSONValue {
    override def toJSON: String = {
      val s = new StringBuilder
      s.append("{")
      s.append {
        v.map {
            case (k, v: JSONValue) =>
              val ss = new StringBuilder
              ss.append("\"")
              ss.append(quoteJSONString(k))
              ss.append("\":")
              ss.append(v.toJSON)
              ss.result()
          }.mkString(",")
      }
      s.append("}")
      s.result()
    }
  }
  final case class JSONArray(v: IndexedSeq[JSONValue]) extends JSONValue {
    override def toJSON: String = {
      val s = new StringBuilder
      s.append("[")
      s.append(v.map(x => x.toJSON).mkString(","))
      s.append("]")
      s.result()
    }
  }

  /**
    * This function can be used to properly quote Strings
    * for JSON output.
    */
  def quoteJSONString(s: String): String = {
    s.map {
      case '"'  => "\\\""
      case '\\' => "\\\\"
//      case '/'  => "\\/" We don't need to escape forward slashes
      case '\b' => "\\b"
      case '\f' => "\\f"
      case '\n' => "\\n"
      case '\r' => "\\r"
      case '\t' => "\\t"
      /* We'll unicode escape any control characters. These include:
       * 0x0 -> 0x1f  : ASCII Control (C0 Control Codes)
       * 0x7f         : ASCII DELETE
       * 0x80 -> 0x9f : C1 Control Codes
       *
       * Per RFC4627, section 2.5, we're not technically required to
       * encode the C1 codes, but we do to be safe.
       */
      case c if ((c >= '\u0000' && c <= '\u001f') || (c >= '\u007f' && c <= '\u009f')) => "\\u%04x".format(c.toInt)
      case c                                                                           => c
    }.mkString
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy