SubwayTooter-Android-App/app/src/main/java/jp/juggler/util/Json.kt

1104 lines
34 KiB
Kotlin

package jp.juggler.util
import java.io.*
import java.math.BigDecimal
import java.math.BigInteger
class JsonException : RuntimeException {
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable) : super(cause.message, cause)
}
private const val char0 = '\u0000'
// Tests if the value should be tried as a decimal.
// It makes no test if there are actual digits.
// return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
private fun String.isDecimalNotation(): Boolean =
indexOf('.') > -1 ||
indexOf('e') > -1 ||
indexOf('E') > -1 ||
this == "-0"
private fun String.stringToNumber(): Number {
val initial = this.firstOrNull()
if (initial != null && (initial >= '0' && initial <= '9' || initial == '-')) {
val length = this.length
when {
isDecimalNotation() -> return if (length > 14) {
BigDecimal(this)
} else {
val d = this.toDouble()
if (d.isInfinite() || d.isNaN()) {
// if we can't parse it as a double, go up to BigDecimal
// this is probably due to underflow like 4.32e-678
// or overflow like 4.65e5324. The size of the string is small
// but can't be held in a Double.
BigDecimal(this)
} else {
d
}
}
length <= 9 -> return this.toInt()
length <= 18 -> return this.toLong()
else -> {
// BigInteger version: We use a similar bitLength compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is
// long lived. Which is the better tradeoff? This is closer to what's
// in stringToValue.
val bi = BigInteger(this)
return when {
bi.bitLength() <= 31 -> bi.toInt()
bi.bitLength() <= 63 -> bi.toLong()
else -> bi
}
}
}
}
throw NumberFormatException("val [$this] is not a valid number.")
}
private fun Any?.asNumber(defaultValue: Number): Number =
when (this) {
null -> defaultValue
is Number -> this
else -> try {
toString().stringToNumber()
} catch (e: Exception) {
defaultValue
}
}
class JsonArray : ArrayList<Any?> {
constructor(capacity: Int = 10) : super(capacity)
constructor(collection: Collection<*>) : super(collection)
constructor(array: Array<*>) : super(array.toList())
fun toString(indentFactor: Int, sort: Boolean = false): String {
val sw = StringWriter()
synchronized(sw.buffer) {
return sw.writeJsonValue(indentFactor, 0, this, sort).toString()
}
}
override fun toString(): String = toString(0)
fun objectList() = mapNotNull { it.cast<JsonObject>() }
fun stringList() = mapNotNull { it?.toString() }
fun stringArrayList() = ArrayList<String>(stringList())
fun floatArrayList() = ArrayList<Float>(this.size).apply {
addAll(this@JsonArray.mapNotNull { this.asNumber(0f).toFloat() })
}
fun string(key: Int): String? = this[key]?.toString()
fun boolean(key: Int): Boolean? = JsonObject.castBoolean(this[key])
fun int(key: Int): Int? = JsonObject.castInt(this[key])
fun long(key: Int): Long? = JsonObject.castLong(this[key])
fun float(key: Int): Float? = JsonObject.castFloat(this[key])
@Suppress("MemberVisibilityCanBePrivate")
fun double(key: Int): Double? = JsonObject.castDouble(this[key])
fun jsonObject(key: Int) = this[key].cast<JsonObject>()
fun jsonArray(key: Int) = this[key].cast<JsonArray>()
fun optString(key: Int, defVal: String = "") = string(key) ?: defVal
fun optBoolean(key: Int, defVal: Boolean = false) = boolean(key) ?: defVal
@Suppress("unused")
fun optInt(key: Int, defVal: Int = 0) = int(key) ?: defVal
@Suppress("unused")
fun optLong(key: Int, defVal: Long = 0L) = long(key) ?: defVal
@Suppress("unused")
fun optFloat(key: Int, defVal: Float = 0f) = float(key) ?: defVal
@Suppress("unused")
fun optDouble(key: Int, defVal: Double = 0.0) = double(key) ?: defVal
@Suppress("unused")
fun notEmptyOrThrow(key: Int) = notEmptyOrThrow(key.toString(), string(key))
@Suppress("unused")
fun isNull(key: Int) = this[key] == null
}
// https://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order/38218582#38218582
// ブラウザはES2015によりオブジェクト列挙順序に挿入順序が影響する
// JSONにそんな規定はないが、MisskeyのAPIはこれに依存した挙動をする
// https://github.com/syuilo/misskey/issues/5684
class JsonObject : LinkedHashMap<String, Any?>() {
companion object {
fun castBoolean(o: Any?): Boolean? =
when (o) {
null -> null
is Boolean -> o
is Int -> o != 0
is Long -> o != 0L
is Float -> !(o.isFinite() && o == 0f)
is Double -> !(o.isFinite() && o == 0.0)
is String -> when (o) {
"", "0", "false", "False" -> false
else -> true
}
is JsonArray -> o.isNotEmpty()
is JsonObject -> o.isNotEmpty()
else -> true
}
fun castLong(o: Any?): Long? =
when (o) {
is Long -> o
is Number -> o.toLong()
is String -> try {
o.stringToNumber().toLong()
} catch (_: NumberFormatException) {
null
}
else -> null // may null or JsonObject.NULL or object,array,boolean
}
fun castInt(o: Any?): Int? =
when (o) {
is Int -> o
is Number -> try {
o.toInt()
} catch (_: NumberFormatException) {
null
}
is String -> try {
o.stringToNumber().toInt()
} catch (_: NumberFormatException) {
null
}
else -> null // may null or JsonObject.NULL or object,array,boolean
}
fun castDouble(o: Any?): Double? =
when (o) {
is Double -> o
is Number -> try {
o.toDouble()
} catch (_: NumberFormatException) {
null
}
is String -> try {
o.stringToNumber().toDouble()
} catch (_: NumberFormatException) {
null
}
else -> null // may null or JsonObject.NULL or object,array,boolean
}
fun castFloat(o: Any?): Float? =
when (o) {
is Float -> o
is Number -> try {
o.toFloat()
} catch (_: NumberFormatException) {
null
}
is String -> try {
o.stringToNumber().toFloat()
} catch (_: NumberFormatException) {
null
}
else -> null // may null or JsonObject.NULL or object,array,boolean
}
}
fun toString(indentFactor: Int, sort: Boolean = false): String {
val sw = StringWriter()
synchronized(sw.buffer) {
return sw.writeJsonValue(indentFactor, 0, this, sort = sort).toString()
}
}
override fun toString(): String = toString(0)
fun string(key: String): String? = this[key]?.toString()
fun boolean(key: String): Boolean? = castBoolean(this[key])
fun int(key: String): Int? = castInt(this[key])
fun long(key: String): Long? = castLong(this[key])
fun float(key: String): Float? = castFloat(this[key])
fun double(key: String): Double? = castDouble(this[key])
fun jsonObject(name: String) = this[name].cast<JsonObject>()
fun jsonArray(name: String) = this[name].cast<JsonArray>()
fun stringArrayList(name: String): ArrayList<String>? =
jsonArray(name)?.stringArrayList()?.notEmpty()
fun floatArrayList(name: String): ArrayList<Float>? =
jsonArray(name)?.floatArrayList()?.notEmpty()
fun optString(name: String, defVal: String = "") = string(name) ?: defVal
fun optBoolean(name: String, defVal: Boolean = false) = boolean(name) ?: defVal
fun optInt(name: String, defVal: Int = 0) = int(name) ?: defVal
fun optLong(name: String, defVal: Long = 0L) = long(name) ?: defVal
fun optFloat(name: String, defVal: Float = 0f) = float(name) ?: defVal
@Suppress("unused")
fun optDouble(name: String, defVal: Double = 0.0) = double(name) ?: defVal
fun stringOrThrow(name: String) = notEmptyOrThrow(name, string(name))
// fun isNull(name : String) = this[name] == null
fun putNotNull(name: String, value: Any?) {
if (value != null) put(name, value)
}
fun putIfTrue(key: String, value: Boolean) {
if (value) put(key, true)
}
}
class JsonTokenizer(reader: Reader) {
companion object {
private fun String.toStringOrNumber(): Any {
/*
* If it might be a number, try converting it. If a number cannot be
* produced, then the value will just be a string.
*/
val initial = this.firstOrNull()
if (initial != null && (initial in '0'..'9' || initial == '-')) {
try { // if we want full Big Number support the contents of this
// `try` block can be replaced with:
// return stringToNumber(string);
if (isDecimalNotation()) {
val d = toDouble()
if (!d.isInfinite() && !d.isNaN()) {
return d
}
} else {
val longValue = toLong()
if (longValue.toString() == this) {
try {
val intValue = longValue.toInt()
if (intValue.toLong() == longValue) return intValue
} catch (_: Throwable) {
// ignored
}
return longValue
}
}
} catch (ignore: Exception) {
}
}
return this
}
}
// constructor(inputStream : InputStream) : this(InputStreamReader(inputStream))
constructor(s: String) : this(StringReader(s))
/** current read character position on the current line. */
private var character = 1L
/** flag to indicate if the end of the input has been found. */
private var eof = false
/** current read index of the input. */
private var index = 0L
/** current line of the input. */
private var line = 1L
/** previous character read from the input. */
private var previous = char0
/** Reader for the input. */
private val reader = if (reader.markSupported()) reader else BufferedReader(reader)
/** flag to indicate that a previous character was requested. */
private var usePrevious = false
/** the number of characters read in the previous line. */
private var characterPreviousLine = 0L
/**
* Back up one character. This provides a sort of lookahead capability,
* so that you can test for a digit or letter before attempting to parse
* the next number or identifier.
* @throws JsonException Thrown if trying to step back more than 1 step
* or if already at the start of the string
*/
private fun back() {
if (usePrevious || index <= 0) {
throw JsonException("Stepping back two steps is not supported")
}
decrementIndexes()
usePrevious = true
eof = false
}
/**
* Decrements the indexes for the [.back] method based on the previous character read.
*/
private fun decrementIndexes() {
index--
if (previous == '\r' || previous == '\n') {
line--
character = characterPreviousLine
} else if (character > 0) {
character--
}
}
/**
* Checks if the end of the input has been reached.
*
* @return true if at the end of the file and we didn't step back
*/
private fun end(): Boolean {
return eof && !usePrevious
}
// /**
// * Determine if the source string still contains characters that next()
// * can consume.
// * @return true if not yet at the end of the source.
// * @throws JsonException thrown if there is an error stepping forward
// * or backward while checking for more data.
// */
// private fun more() : Boolean {
// if(usePrevious) {
// return true
// }
// try {
// reader.mark(1)
// } catch(e : IOException) {
// throw JsonException("Unable to preserve stream position", e)
// }
// try { // -1 is EOF, but next() can not consume the null character '\0'
// if(reader.read() <= 0) {
// eof = true
// return false
// }
// reader.reset()
// } catch(e : IOException) {
// throw JsonException("Unable to read the next character from the stream", e)
// }
// return true
// }
/**
* Get the next character in the source string.
*
* @return The next character, or 0 if past the end of the source string.
* @throws JsonException Thrown if there is an error reading the source string.
*/
private operator fun next(): Char {
val c: Char
if (usePrevious) {
usePrevious = false
c = previous
} else {
val i = try {
reader.read()
} catch (exception: IOException) {
throw JsonException(exception)
}
if (i <= 0) { // End of stream
eof = true
return char0
}
c = i.toChar()
}
incrementIndexes(c)
previous = c
return c
}
/**
* Increments the internal indexes according to the previous character
* read and the character passed as the current character.
* @param c the current character read.
*/
private fun incrementIndexes(c: Char) {
if (c == char0) return
index++
when (c) {
'\r' -> {
line++
characterPreviousLine = character
character = 0
}
'\n' -> {
if (previous != '\r') {
line++
characterPreviousLine = character
}
character = 0
}
else -> {
character++
}
}
}
// /**
// * Consume the next character, and check that it matches a specified
// * character.
// * @param c The character to match.
// * @return The character.
// * @throws JsonException if the character does not match.
// */
// private fun next(c : Char) : Char {
// val n = this.next()
// if(n != c) {
// if(n.toInt() > 0) {
// throw this.syntaxError(
// "Expected '" + c + "' and instead saw '" +
// n + "'"
// )
// }
// throw this.syntaxError("Expected '$c' and instead saw ''")
// }
// return n
// }
/**
* Get the next n characters.
*
* @param n The number of characters to take.
* @return A string of n characters.
* @throws JsonException
* Substring bounds error if there are not
* n characters remaining in the source string.
*/
private fun next(@Suppress("SameParameterValue") n: Int): String {
if (n == 0) {
return ""
}
val chars = CharArray(n)
var pos = 0
while (pos < n) {
chars[pos] = this.next()
if (end()) {
throw this.syntaxError("Substring bounds error")
}
pos += 1
}
return String(chars)
}
/**
* Get the next char in the string, skipping whitespace.
* @throws JsonException Thrown if there is an error reading the source string.
* @return A character, or 0 if there are no more characters.
*/
private fun nextClean(): Char {
while (true) {
val c = this.next()
if (c == char0 || c > ' ') {
return c
}
}
}
/**
* Return the characters up to the next close quote character.
* Backslash processing is done. The formal JSON format does not
* allow strings in single quotes, but an implementation is allowed to
* accept them.
* @param quote The quoting character, either
* `"`&nbsp;<small>(double quote)</small> or
* `'`&nbsp;<small>(single quote)</small>.
* @return A String.
* @throws JsonException Unterminated string.
*/
private fun nextString(quote: Char): String {
val sb = StringBuilder()
while (true) {
var c: Char = this.next()
when (c) {
char0, '\n', '\r' ->
throw this.syntaxError("Unterminated string")
quote ->
return sb.toString()
'\\' -> {
c = this.next()
when (c) {
'b' -> sb.append('\b')
't' -> sb.append('\t')
'n' -> sb.append('\n')
'f' -> sb.append('\u000c')
'r' -> sb.append('\r')
'u' -> try {
sb.append(this.next(4).toInt(16).toChar())
} catch (e: NumberFormatException) {
throw syntaxError("Illegal escape.", e)
}
'"', '\'', '\\', '/' -> sb.append(c)
else -> throw syntaxError("Illegal escape.")
}
}
else -> sb.append(c)
}
}
}
// /**
// * Get the text up but not including the specified character or the
// * end of line, whichever comes first.
// * @param delimiter A delimiter character.
// * @return A string.
// * @throws JsonException Thrown if there is an error while searching
// * for the delimiter
// */
// fun nextTo(delimiter : Char) : String {
// val sb = StringBuilder()
// while(true) {
// val c = this.next()
// if(c == delimiter || c == char0 || c == '\n' || c == '\r') {
// if(c != char0) {
// back()
// }
// return sb.toString().trim()
// }
// sb.append(c)
// }
// }
// /**
// * Get the text up but not including one of the specified delimiter
// * characters or the end of line, whichever comes first.
// * @param delimiters A set of delimiter characters.
// * @return A string, trimmed.
// * @throws JsonException Thrown if there is an error while searching
// * for the delimiter
// */
// fun nextTo(delimiters : String) : String {
// val sb = StringBuilder()
// while(true) {
// val c = this.next()
// if(delimiters.indexOf(c) >= 0 || c == char0 || c == '\n' || c == '\r') {
// if(c != char0) {
// back()
// }
// return sb.toString().trim { it <= ' ' }
// }
// sb.append(c)
// }
// }
/**
* Get the next value. The value can be a Boolean, Double, Integer,
* JsonArray, JsonObject, Long, or String, or the JsonObject.NULL object.
* @throws JsonException If syntax error.
*
* @return An object.
*/
fun nextValue(): Any? {
var c = nextClean()
val string: String
when (c) {
'"', '\'' -> return nextString(c)
'{' -> {
back()
return parseInto(JsonObject())
}
'[' -> {
back()
return parseInto(JsonArray())
}
}
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
val sb = StringBuilder()
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c)
c = this.next()
}
if (!eof) {
back()
}
string = sb.toString().trim { it <= ' ' }
if ("" == string) {
throw syntaxError("Missing value")
}
return with(string) {
when {
isEmpty() -> ""
equals("true", ignoreCase = true) -> true
equals("false", ignoreCase = true) -> false
equals("null", ignoreCase = true) -> null
else -> toStringOrNumber()
}
}
}
// /**
// * Skip characters until the next character is the requested character.
// * If the requested character is not found, no characters are skipped.
// * @param to A character to skip to.
// * @return The requested character, or zero if the requested character
// * is not found.
// * @throws JsonException Thrown if there is an error while searching
// * for the to character
// */
// @Throws(JsonException::class)
// fun skipTo(to : Char) : Char {
// var c : Char
// try {
// val startIndex = index
// val startCharacter = character
// val startLine = line
// reader.mark(1000000)
// do {
// c = this.next()
// if(c.toInt() == 0) { // in some readers, reset() may throw an exception if
// // the remaining portion of the input is greater than
// // the mark size (1,000,000 above).
// reader.reset()
// index = startIndex
// character = startCharacter
// line = startLine
// return char0
// }
// } while(c != to)
// reader.mark(1)
// } catch(exception : IOException) {
// throw JsonException(exception)
// }
// back()
// return c
// }
/**
* Make a JsonException to signal a syntax error.
*
* @param message The error message.
* @return A JsonException object, suitable for throwing
*/
private fun syntaxError(message: String): JsonException {
return JsonException(message + this.toString())
}
/**
* Make a JsonException to signal a syntax error.
*
* @param message The error message.
* @param causedBy The throwable that caused the error.
* @return A JsonException object, suitable for throwing
*/
private fun syntaxError(
@Suppress("SameParameterValue") message: String,
causedBy: Throwable?
) = JsonException(message + toString(), causedBy)
/**
* Make a printable string of this JSONTokener.
*
* @return " at {index} [character {character} line {line}]"
*/
override fun toString(): String =
" at $index [character $character line $line]"
private fun parseInto(dst: JsonObject): JsonObject {
if (nextClean() != '{')
throw syntaxError("A JsonObject text must begin with '{'")
while (true) {
var c: Char = nextClean()
val key: String = when (c) {
char0 ->
throw syntaxError("A JsonObject text must end with '}'")
'}' ->
return dst
else -> {
back()
nextValue().toString()
}
}
// The key is followed by ':'.
c = nextClean()
if (c != ':')
throw syntaxError("Expected a ':' after a key")
// Use syntaxError(..) to include error location
// Check if key exists
// key already exists
if (dst.contains(key))
throw syntaxError("Duplicate key \"$key\"")
// Only add value if non-null
dst[key] = nextValue()
when (nextClean()) {
';', ',' -> {
if (nextClean() == '}') {
return dst
}
back()
}
'}' -> return dst
else -> throw syntaxError("Expected a ',' or '}'")
}
}
}
private fun parseInto(dst: JsonArray): JsonArray {
if (nextClean() != '[')
throw syntaxError("A JsonArray text must start with '['")
when (nextClean()) {
// array is unclosed. No ']' found, instead EOF
char0 -> throw syntaxError("Expected a ',' or ']'")
// empty array
']' -> return dst
else -> {
back()
while (true) {
if (nextClean() == ',') {
back()
dst.add(null)
} else {
back()
dst.add(nextValue())
}
when (nextClean()) {
char0 -> throw syntaxError("Expected a ',' or ']'")
']' -> return dst
',' -> when (nextClean()) {
// array is unclosed. No ']' found, instead EOF
char0 -> throw syntaxError("Expected a ',' or ']'")
']' -> return dst
else -> back()
}
else -> throw syntaxError("Expected a ',' or ']'")
}
}
}
}
}
}
private val reNumber = """-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?""".asciiPattern()
private fun Writer.writeQuote(string: String): Writer {
if (string.isEmpty()) {
write("\"\"")
} else {
append('"')
var previousChar: Char = char0
for (c in string) {
when (c) {
'\\', '"' -> {
append('\\')
append(c)
}
'/' -> {
if (previousChar == '<') append('\\')
append(c)
}
'\b' -> append("\\b")
'\t' -> append("\\t")
'\n' -> append("\\n")
'\u000c' -> append("\\f")
'\r' -> append("\\r")
in char0 until ' ',
in '\u0080' until '\u00a0',
in '\u2000' until '\u2100' -> {
write("\\u")
val hexCode: String = Integer.toHexString(c.code)
write("0000", 0, 4 - hexCode.length)
write(hexCode)
}
else -> append(c)
}
previousChar = c
}
append('"')
}
return this
}
private fun Number.toJsonString(): String {
when (this) {
is Double -> if (isInfinite() || isNaN())
throw JsonException("JSON does not allow non-finite numbers.")
is Float -> if (isInfinite() || isNaN())
throw JsonException("JSON does not allow non-finite numbers.")
}
// Shave off trailing zeros and decimal point, if possible.
var string = toString()
if (string.indexOf('.') > 0 &&
string.indexOf('e') < 0 &&
string.indexOf('E') < 0
) {
while (string.endsWith("0")) {
string = string.substring(0, string.length - 1)
}
if (string.endsWith(".")) {
string = string.substring(0, string.length - 1)
}
}
return string
}
private fun Writer.indent(indentFactor: Int, indent: Int): Writer {
if (indentFactor > 0) {
append('\n')
for (i in 0 until indent) append(' ')
}
return this
}
private fun Writer.writeCollection(indentFactor: Int, indent: Int, src: Collection<*>, sort: Boolean): Writer =
try {
append('[')
when (src.size) {
0 -> {
}
1 -> try {
writeJsonValue(indentFactor, indent, src.iterator().next(), sort)
} catch (e: Exception) {
throw JsonException("Unable to write JsonArray value at index: 0", e)
}
else -> {
val newIndent = indent + indentFactor
for ((index, value) in src.withIndex()) {
if (index > 0) append(',')
indent(indentFactor, newIndent)
try {
writeJsonValue(indentFactor, newIndent, value, sort)
} catch (ex: Exception) {
throw JsonException("Unable to write JsonArray value at index: $index", ex)
}
}
indent(indentFactor, indent)
}
}
append(']')
this
} catch (e: IOException) {
throw JsonException(e)
}
private fun Writer.writeArray(indentFactor: Int, indent: Int, src: Any, sort: Boolean): Writer =
try {
append('[')
when (val size = java.lang.reflect.Array.getLength(src)) {
0 -> {
}
1 -> try {
val value = java.lang.reflect.Array.get(src, 0)
writeJsonValue(indentFactor, indent, value, sort)
} catch (e: Exception) {
throw JsonException("Unable to write JsonArray value at index: 0", e)
}
else -> {
val newIndent = indent + indentFactor
for (index in 0 until size) {
if (index > 0) append(',')
indent(indentFactor, newIndent)
try {
val value = java.lang.reflect.Array.get(src, index)
writeJsonValue(indentFactor, newIndent, value, sort)
} catch (ex: Exception) {
throw JsonException("Unable to write JsonArray value at index: $index", ex)
}
}
indent(indentFactor, indent)
}
}
append(']')
this
} catch (e: IOException) {
throw JsonException(e)
}
private fun Writer.writeMap(indentFactor: Int, indent: Int, src: Map<*, *>, sort: Boolean): Writer =
try {
append('{')
when (src.size) {
0 -> {
}
1 -> {
val entry = src.entries.first()
writeJsonValue(indentFactor, indent, entry.key, sort)
append(':')
if (indentFactor > 0) append(' ')
try {
writeJsonValue(indentFactor, indent, entry.value, sort)
} catch (ex: Throwable) {
throw JsonException(
"Unable to write JsonObject value for key: ${entry.key}",
ex
)
}
}
else -> {
val newIndent = indent + indentFactor
var needsComma = false
val entries = if (sort) {
src.entries.sortedBy { it.key.toString() }
} else {
src.entries
}
for (entry in entries) {
if (needsComma) append(',')
indent(indentFactor, newIndent)
writeJsonValue(indentFactor, newIndent, entry.key, sort)
append(':')
if (indentFactor > 0) append(' ')
try {
writeJsonValue(indentFactor, newIndent, entry.value, sort)
} catch (ex: Exception) {
throw JsonException(
"Unable to write JsonObject value for key: ${entry.key}",
ex
)
}
needsComma = true
}
indent(indentFactor, indent)
}
}
append('}')
this
} catch (e: IOException) {
throw JsonException(e)
}
fun Writer.writeJsonValue(
indentFactor: Int,
indent: Int,
value: Any?,
sort: Boolean
): Writer {
when {
value == null -> write("null")
value is Boolean -> write(value.toString())
value is Number -> {
val sv = value.toJsonString()
if (reNumber.matcher(sv).matches()) {
write(sv)
} else {
// not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
// The Number value is not a valid JSON number.
// Instead we will quote it as a string
writeQuote(sv)
}
}
value is Char -> writeJsonValue(indentFactor, indent, value.code, sort = sort)
value is String -> writeQuote(value)
value is Enum<*> -> writeQuote(value.name)
value is JsonObject -> writeMap(indentFactor, indent, value, sort = sort)
value is Map<*, *> -> writeMap(indentFactor, indent, value, sort = sort)
value is Collection<*> -> writeCollection(indentFactor, indent, value, sort = sort)
value.javaClass.isArray -> writeArray(indentFactor, indent, value, sort = sort)
else -> writeQuote(value.toString())
}
return this
}
/////////////////////////////////////////////////////////////////////////////
fun notEmptyOrThrow(name: String, value: String?) =
if (value?.isNotEmpty() == true) value else throw RuntimeException("$name is empty")
private val log = LogCategory("Json")
// return null if the json value is "null"
fun String.decodeJsonValue() = try {
JsonTokenizer(this).nextValue()
} catch (ex: Throwable) {
log.e(ex, "decodeJsonValue failed. $this")
throw ex
}
//fun String.parseJsonOrNull() =try {
// decodeJsonValue()
//}catch(ex:Throwable){
// log.e(ex,"decodeJsonValue() failed.")
// null
//}
fun String.decodeJsonObject() = decodeJsonValue()!!.castNotNull<JsonObject>()
fun String.decodeJsonArray() = decodeJsonValue()!!.castNotNull<JsonArray>()
fun Array<*>.toJsonArray(): JsonArray = JsonArray(this)
fun List<*>.toJsonArray() = JsonArray(this)
inline fun jsonObject(initializer: JsonObject.() -> Unit) =
JsonObject().apply { initializer() }
inline fun jsonArray(initializer: JsonArray.() -> Unit) =
JsonArray().apply { initializer() }
fun jsonArrayOf(vararg args: Any) = JsonArray(args)
fun jsonObjectOf(vararg args: Pair<String, *>) = JsonObject().apply {
for (pair in args) {
put(pair.first, pair.second)
}
}