
wjson.JsValue.scala Maven / Gradle / Ivy
package wjson
import wjson.JsValue.{JsArray, JsNumber}
import scala.annotation.targetName
import scala.collection.mutable
/**
* Json Model ADT
*/
enum JsValue:
case JsNull
case JsBoolean(value: Boolean)
case JsNumber(value: Double|Long)
case JsString(value: String)
case JsArray(elements: Seq[JsValue])
case JsObject(fields: Seq[(String, JsValue)]) // changed, preserve the order of fields
// compare JsValue ignore the order of fields
override def equals(obj: Any): Boolean = obj match
case x: JsBoolean => this.isInstanceOf[JsBoolean] && x.value == this.asInstanceOf[JsBoolean].value
case x: JsNumber => this.isInstanceOf[JsNumber] && x.value == this.asInstanceOf[JsNumber].value
case x: JsString => this.isInstanceOf[JsString] && x.value == this.asInstanceOf[JsString].value
case x: JsArray => this.isInstanceOf[JsArray] && x.elements == this.asInstanceOf[JsArray].elements
case x: JsObject => this.isInstanceOf[JsObject] && x.fields.toMap == this.asInstanceOf[JsObject].fields.toMap
case x: JsValue => this.eq(JsNull)
case _ => false
object JsValue:
val JsTrue: JsBoolean = JsBoolean(true)
val JsFalse: JsBoolean = JsBoolean(false)
val JsZero: JsNumber = JsNumber(0L)
val JsEmptyString: JsString = JsString("")
val JsEmptyObject: JsObject = new JsObject(Seq.empty)
val JsEmptyArray: JsArray = new JsArray(Seq.empty)
def JsNumber(value: Int): JsNumber = JsNumber(value.toLong)
def parseJson(str: String): JsValue = JsonParser.parse(ParserInput(str))
def parseJson5(str: String): JsValue = new Json5Parser(ParserInput(str)).parseJsValue()
def obj(fields: (String, JsValue)*): JsObject = JsObject(fields)
def arr(elements: JsValue*): JsArray = JsArray(elements)
@deprecated("using parseJson(str)")
def parse(str: String): JsValue = JsonParser.parse(ParserInput(str))
@deprecated("using obj(fields)")
def JsObject(fields: (String, JsValue)*): JsObject = JsObject(fields)
@deprecated("using arr(elements)")
def JsArray(elements: JsValue*): JsArray = JsArray(elements)
extension (value: JsObject)
def contains(name: String): Boolean = value.fields.exists(_._1 == name)
def field(name: String): JsValue = value.fields.find(_._1 == name).map(_._2).get
def fieldOpt(name: String): Option[JsValue] = value.fields.find(_._1 == name).map(_._2)
def keys(): Seq[String] = value.fields.map(_._1)
def merge(kvs: (String, JsValue)*): JsObject =
val keys1 = value.fields.map(_._1).toSet
val keys2 = kvs.map(_._1).toSet
val duplicateKeys = keys1.intersect(keys2)
if duplicateKeys.nonEmpty then
JsObject(value.fields.filterNot(x => duplicateKeys.contains(x._1)) ++ kvs)
else JsObject(value.fields ++ kvs)
@targetName("appendObject")
def ++(other: JsObject): JsObject = merge(other.fields:_*)
@targetName("appendSeq")
def ++(other: Seq[(String, JsValue)]): JsObject = merge(other:_*)
def remove(name: String*): JsObject =
JsObject(value.fields.filterNot(x => name.contains(x._1)))
@targetName("append")
def +(kv: (String, JsValue)): JsObject =
if value.fields.exists(_._1 == kv._1) then
JsObject(value.fields.filterNot(_._1 == kv._1) :+ kv)
else JsObject(value.fields :+ kv)
extension (value: JsArray)
@targetName("appendItem")
def :+(elem: JsValue): JsArray = JsArray(value.elements :+ elem)
@targetName("append")
def ++(other: JsArray): JsArray = JsArray(value.elements ++ other.elements)
@targetName("appendSeq")
def ++(other: Seq[JsValue]): JsArray = JsArray(value.elements ++ other)
@targetName("prependItem")
def +:(elem: JsValue): JsArray = JsArray(elem +: value.elements)
def apply(index: Int): JsValue = value.elements(index)
def updated(index: Int, elem: JsValue): JsArray =
JsArray(value.elements.updated(index, elem))
extension (value: JsValue)
def show: String = show(0)
def showPretty: String = show(indent = 2)
def asStr: JsString = value match
case x: JsString => x
case _ => throw new Exception(s"expect JsString, but got $value")
def asNum: JsNumber = value match
case x: JsNumber => x
case _ => throw new Exception(s"expect JsNumber, but got $value")
def asArr: JsArray = value match
case x: JsArray => x
case _ => throw new Exception(s"expect JsArray, but got $value")
def asObj : JsObject = value match
case x: JsObject => x
case _ => throw new Exception(s"expect JsObject, but got $value")
def asBool : JsBoolean = value match
case x: JsBoolean => x
case _ => throw new Exception(s"expect JsBoolean, but got $value")
@deprecated("using show(ident)")
def show(indent: Int, margin: Int = 100): String =
show(indent);
def show(indent: Int): String =
val buffer = new StringBuilder
val cr = if indent > 0 then "\n" else "" // when no ident, dont crlf
def show0(value: JsValue, indentString: String): Unit =
value match
case JsNull => buffer.append("null")
case JsBoolean(value) => buffer.append(value)
case JsNumber(value) => buffer.append(value)
case JsString(value) => buffer.append(escapedString(value))
case JsObject(fields) =>
buffer.append("{").append(cr)
val pos = buffer.length
fields.foreach { case (name, value) =>
if(buffer.length > pos) {
buffer.append(",").append(cr)
}
buffer.append(indentString + " " * indent)
buffer.append(escapedString(name))
buffer.append(":")
show0(value, indentString + " " * indent)
}
buffer.append(cr).append(indentString).append("}")
case JsArray(elements) =>
buffer.append("[").append(cr)
val pos = buffer.length
elements.foreach { elem =>
if buffer.length > pos then buffer.append(",").append(cr)
buffer.append( indentString + " " * indent)
show0(elem, indentString + " " * indent)
}
buffer.append(cr).append(indentString).append("]")
def escapedString(str: String): String =
val sb = new StringBuilder()
sb.append('"')
str.foreach {
case '\\' => sb.append("\\\\")
case '"' => sb.append("\\\"")
case '\b' => sb.append("\\b")
case '\f' => sb.append("\\f")
case '\n' => sb.append("\\n")
case '\r' => sb.append("\\r")
case '\t' => sb.append("\\t")
// case x if x > 0x100 => sb.append("\\u%04x".format(x.toInt))
case c => sb.append(c)
}
sb.append('"')
sb.toString()
show0(value, "")
buffer.toString
export JsValue.{JsNull, JsBoolean, JsNumber, JsString, JsArray, JsObject}
extension (str:String)
def parseJson: JsValue = JsValue.parseJson(str)
def parseJson5: JsValue = JsValue.parseJson5(str)
extension (sc: StringContext)
def json = new JsonInterpolation(sc)
def json5 = new Json5Interpolation(sc)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy