Coverage Summary for Class: RawJson (com.ghost.serialization.types)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| RawJson |
94.4%
(17/18)
|
89.3%
(25/28)
|
95%
(38/40)
|
97.5%
(192/197)
|
| RawJson$Companion |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(17/17)
|
| Total |
95.2%
(20/21)
|
89.3%
(25/28)
|
95.3%
(41/43)
|
97.7%
(209/214)
|
package com.ghost.serialization.types
/**
* Opaque JSON held as verbatim UTF-8 bytes of the wire representation.
*
* Ghost serializes and deserializes [RawJson] as an inline JSON value (object, array,
* string, number, boolean, or null) using zero-copy capture when parsed from a
* [ByteArray] source, without building an intermediate parse tree.
*
* Prefer [RawJson] over [kotlin.ByteArray] on public model fields: it documents intent
* and provides value-based [equals] / [hashCode].
*
* When captured from a flat byte reader, [storage], [storageOffset], and [storageLength]
* alias the parse input buffer until [bytes] is accessed (which materializes an
* exact-length copy for slice values).
*/
class RawJson internal constructor(
val storage: ByteArray,
val storageOffset: Int,
val storageLength: Int
) {
/**
* Exact-length UTF-8 payload. Materializes a copy when this value is a slice into
* a larger [storage] buffer.
*/
val bytes: ByteArray
get() = if (storageOffset == 0 && storageLength == storage.size) {
storage
} else {
storage.copyOfRange(storageOffset, storageOffset + storageLength)
}
/** Decodes the captured UTF-8 JSON bytes as a [String] (wire form, including quotes for strings). */
fun decodeToString(): String =
storage.decodeToString(storageOffset, storageOffset + storageLength)
/** Exclusive end index of this slice in [storage] (`storageOffset + storageLength`). */
val endExclusive: Int
get() = storageOffset + storageLength
/** Classifies the JSON value without parsing or copying the payload. */
fun kind(): RawJsonKind = RawJsonValueScanner.kind(this)
/** `true` when the payload is the JSON literal `null`. */
val isJsonNull: Boolean
get() = RawJsonValueScanner.isJsonNull(this)
/** `true`/`false` for JSON booleans; `null` for `null`, non-boolean, or invalid payloads. */
fun asBooleanOrNull(): Boolean? = RawJsonValueScanner.asBooleanOrNull(this)
/** JSON integer when the payload is a number without fraction or exponent; otherwise `null`. */
fun asIntOrNull(): Int? = RawJsonValueScanner.asIntOrNull(this)
/** JSON integer when the payload is a number without fraction or exponent; otherwise `null`. */
fun asLongOrNull(): Long? = RawJsonValueScanner.asLongOrNull(this)
/** JSON number as [Double]; integer path is zero-allocation, fraction/exponent uses UTF-8 decode once. */
fun asDoubleOrNull(): Double? = RawJsonValueScanner.asDoubleOrNull(this)
/**
* Decoded string contents when the payload is a JSON string (`"..."`); otherwise `null`.
* ASCII fast path avoids escape scanning allocations when no `\` is present.
*/
fun asStringOrNull(): String? = RawJsonValueScanner.asStringOrNull(this)
/**
* Human-readable scalar for UI (capability status, labels). Strings are unquoted;
* numbers/booleans/null use wire text; objects/arrays return full JSON text.
*/
fun asDisplayString(): String = RawJsonValueScanner.asDisplayString(this)
/** Value-based equality for the underlying JSON bytes. */
fun contentEquals(other: RawJson?): Boolean {
if (other == null) return false
if (storageLength != other.storageLength) return false
if (isFullStorageSpan() && other.isFullStorageSpan()) {
return storage.contentEquals(other.storage)
}
val end = storageOffset + storageLength
var otherIndex = other.storageOffset
for (index in storageOffset until end) {
if (storage[index] != other.storage[otherIndex++]) {
return false
}
}
return true
}
/** Value-based hash for the underlying JSON bytes. */
fun contentHashCode(): Int {
if (isFullStorageSpan()) {
return storage.contentHashCode()
}
var result = CONTENT_HASH_SEED
val end = storageOffset + storageLength
for (index in storageOffset until end) {
result = CONTENT_HASH_MULTIPLIER * result + storage[index]
}
return result
}
private fun isFullStorageSpan(): Boolean =
storageOffset == 0 && storageLength == storage.size
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is RawJson) return false
return contentEquals(other)
}
override fun hashCode(): Int = contentHashCode()
override fun toString(): String = "RawJson(${decodeToString()})"
companion object {
/** Initial accumulator for [contentHashCode]; matches [ByteArray.contentHashCode]. */
private const val CONTENT_HASH_SEED = 1
/** Multiplier for [contentHashCode]; matches `java.util.Arrays.hashCode` and [String.hashCode]. */
private const val CONTENT_HASH_MULTIPLIER = 31
/** Wraps an owned UTF-8 buffer exactly as it appears in JSON. */
fun fromUtf8Bytes(bytes: ByteArray): RawJson = RawJson(bytes, 0, bytes.size)
/** Wraps a slice of an existing buffer without copying (flat-reader capture path). */
fun fromBufferSlice(buffer: ByteArray, offset: Int, length: Int): RawJson =
RawJson(buffer, offset, length)
/** Encodes [json] to UTF-8 bytes. For round-trip tests, prefer wire capture. */
fun fromString(json: String): RawJson = fromUtf8Bytes(json.encodeToByteArray())
}
}