package com.suno.android.common_data import javax.inject.Inject /** * Extracts field values from partial/incomplete JSON strings during streaming. * Handles cases where JSON is incomplete or malformed. */ @Suppress("MagicNumber") class PartialJsonExtractor @Inject constructor() { /** * Extracts the value of a specific field from partial JSON. * * Example: `{"message":"Hello"}` -> "Hello" * Example: `{"message":"Hello" (incomplete)` -> "Hello" * Example: `{"message":"He` -> "He" * * @param partialJson The incomplete JSON string * @param fieldName The field name to extract (e.g., "message") * @return The extracted field value, or null if not found or not extractable */ fun extractField( partialJson: String, fieldName: String, ): String? { // Look for the field name pattern: "fieldName":" val fieldPattern = "\"$fieldName\":" val fieldIndex = partialJson.indexOf(fieldPattern) if (fieldIndex == -1) return null // Find the start of the value after the field name val valueStartIndex = fieldIndex + fieldPattern.length if (valueStartIndex >= partialJson.length) return null // Skip whitespace after the colon var currentIndex = valueStartIndex while (currentIndex < partialJson.length && partialJson[currentIndex].isWhitespace()) { currentIndex++ } if (currentIndex >= partialJson.length) return null return if (partialJson[currentIndex] == '"') { extractStringValue(json = partialJson, startIndex = currentIndex + 1) } else { extractNonStringValue(json = partialJson, startIndex = currentIndex) } } private fun extractStringValue( json: String, startIndex: Int, ): String? { val result = StringBuilder() var i = startIndex var escaped = false while (i < json.length) { val char = json[i] when { escaped -> { when (char) { 'n' -> result.append('\n') 't' -> result.append('\t') 'r' -> result.append('\r') 'b' -> result.append('\b') '"', '\\', '/' -> result.append(char) 'u' -> { // Unicode escape (incomplete handling for partial JSON) if (i + 4 < json.length) { val unicodeHex = json.substring(i + 1, i + 5) try { result.append(unicodeHex.toInt(16).toChar()) i += 4 } catch (e: NumberFormatException) { result.append("\\u") } } else { // Incomplete unicode escape, keep as-is result.append("\\u") } } else -> { // Unknown escape, keep the char result.append(char) } } escaped = false } char == '\\' -> { escaped = true } char == '"' -> { // End of string value found return result.toString() } else -> { result.append(char) } } i++ } // String is incomplete (no closing quote found) // Return what we have so far return result.toString() } private fun extractNonStringValue( json: String, startIndex: Int, ): String? { val result = StringBuilder() var i = startIndex while (i < json.length) { val char = json[i] when { char.isWhitespace() || char == ',' || char == '}' || char == ']' -> { // End of non-string value return result.toString().takeIf { it.isNotEmpty() } } else -> result.append(char) } i++ } // Value extends to end of string return result.toString().takeIf { it.isNotEmpty() } } }