Skip to content

Commit

Permalink
Merge pull request #7 from ding1dingx/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
syxc authored Aug 25, 2024
2 parents 5fb4275 + be225ad commit 4b52011
Show file tree
Hide file tree
Showing 5 changed files with 410 additions and 37 deletions.
20 changes: 15 additions & 5 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: CI

on:
pull_request: { }
workflow_dispatch: { }
pull_request: {}
workflow_dispatch: {}
push:
branches:
- master
Expand All @@ -13,7 +13,7 @@ concurrency:
cancel-in-progress: true

jobs:
build:
build-and-test:
runs-on: ubuntu-latest

steps:
Expand All @@ -23,11 +23,21 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'corretto'
java-version: "17"
distribution: "corretto"

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build with Gradle
run: ./gradlew build

- name: Run tests
run: ./gradlew test

- name: Upload test results
uses: actions/upload-artifact@v2
if: always()
with:
name: test-results
path: "**/build/test-results/test"
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ appcompat = "1.7.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
robolectric = "4.8"

[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
Expand All @@ -18,6 +19,7 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a
junit = { module = "junit:junit", version.ref = "junit" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }

ktlint = "com.pinterest.ktlint:ktlint-cli:1.3.1"

Expand Down
1 change: 1 addition & 0 deletions library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {
compileOnly(libs.androidx.appcompat)
// Testing dependencies
testImplementation(libs.junit)
testImplementation(libs.robolectric)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
115 changes: 83 additions & 32 deletions library/src/main/java/com/ding1ding/jsbridge/JsonUtils.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("ktlint:standard:max-line-length")

package com.ding1ding.jsbridge

import java.math.BigInteger
Expand All @@ -16,61 +18,85 @@ object JsonUtils {
fun toJson(any: Any?): String = when (any) {
null -> "null"
is JSONObject, is JSONArray -> any.toString()
is String -> JSONObject.quote(any)
is String -> JSONObject.quote(any) // Use JSONObject.quote to handle special characters correctly
is Boolean, is Number -> any.toString()
is Date -> JSONObject.quote(isoDateFormat.format(any))
is Map<*, *> -> any.toJsonObject().toString()
is Collection<*> -> any.toJsonArray().toString()
is Map<*, *> -> mapToJson(any)
is Collection<*> -> collectionToJson(any)
is Enum<*> -> JSONObject.quote(any.name)
else -> try {
any::class.java.declaredFields
.filter { !it.isSynthetic }
.associate { field ->
field.isAccessible = true
field.name to field.get(any)
}.toJsonObject().toString()
objectToJson(any)
} catch (e: Exception) {
Logger.e(e) { "Failed to serialize object of type ${any::class.java.simpleName}" }
JSONObject.quote(any.toString())
}
}

private fun Map<*, *>.toJsonObject() = JSONObject().apply {
forEach { (key, value) ->
put(key.toString(), value?.let { toJsonValue(it) } ?: JSONObject.NULL)
private fun mapToJson(map: Map<*, *>): String {
val jsonObject = JSONObject()
for ((key, value) in map) {
jsonObject.put(key.toString(), toJsonValue(value))
}
return jsonObject.toString()
}

private fun Collection<*>.toJsonArray() = JSONArray(
map {
it?.let { toJsonValue(it) }
?: JSONObject.NULL
},
)
private fun collectionToJson(collection: Collection<*>): String {
val jsonArray = JSONArray()
for (item in collection) {
jsonArray.put(toJsonValue(item))
}
return jsonArray.toString()
}

private fun toJsonValue(value: Any): Any = when (value) {
is JSONObject, is JSONArray, is String, is Boolean, is Number -> value
private fun objectToJson(obj: Any): String {
val jsonObject = JSONObject()
obj::class.java.declaredFields
.filter { !it.isSynthetic }
.forEach { field ->
field.isAccessible = true
jsonObject.put(field.name, toJsonValue(field.get(obj)))
}
return jsonObject.toString()
}

private fun toJsonValue(value: Any?): Any? = when (value) {
null -> JSONObject.NULL
is JSONObject, is JSONArray, is String, is Boolean, is Number, is Char -> value
is Map<*, *> -> JSONObject(mapToJson(value))
is Collection<*> -> JSONArray(collectionToJson(value))
is Date -> isoDateFormat.format(value)
is Enum<*> -> value.name
else -> toJson(value)
}

fun fromJson(json: String): Any? = try {
when {
json == "null" -> null
json.startsWith("{") && json.endsWith("}") -> JSONObject(json).toMap()
json.startsWith("[") && json.endsWith("]") -> JSONArray(json).toList()
json.startsWith("\"") && json.endsWith("\"") -> json.unquote().let { unquoted ->
tryParseDate(unquoted) ?: unquoted
json.startsWith("{") && json.endsWith("}") -> parseJsonObject(json)
json.startsWith("[") && json.endsWith("]") -> parseJsonArray(json)
json.startsWith("\"") && json.endsWith("\"") -> {
val unquoted = json.substring(1, json.length - 1)
tryParseDate(unquoted) ?: unescapeString(unquoted)
}

json == "true" -> true
json == "false" -> false
else -> parseNumber(json)
else -> parseNumber(json) ?: json
}
} catch (e: Exception) {
Logger.e(e) { "Error parsing JSON: $json" }
json // Return the original string if parsing fails
}

private fun parseJsonObject(json: String): Map<String, Any?> =
JSONObject(json).keys().asSequence().associateWith { key ->
fromJson(JSONObject(json).get(key).toString())
}

private fun parseJsonArray(json: String): List<Any?> = JSONArray(json).let { array ->
(0 until array.length()).map { fromJson(array.get(it).toString()) }
}

private fun JSONObject.toMap(): Map<String, Any?> = keys().asSequence().associateWith { key ->
when (val value = get(key)) {
is JSONObject -> value.toMap()
Expand All @@ -89,7 +115,7 @@ object JsonUtils {
}
}

private fun parseNumber(value: String): Any = when {
private fun parseNumber(value: String): Any? = when {
value.contains(".") || value.lowercase(Locale.US).contains("e") -> {
try {
val doubleValue = value.toDouble()
Expand All @@ -99,7 +125,7 @@ object JsonUtils {
else -> doubleValue
}
} catch (e: NumberFormatException) {
value
null
}
}

Expand All @@ -114,7 +140,7 @@ object JsonUtils {
try {
BigInteger(value)
} catch (e: NumberFormatException) {
value
null
}
}
}
Expand All @@ -126,9 +152,34 @@ object JsonUtils {
null
}

private fun String.unquote(): String = if (length >= 2 && startsWith('"') && endsWith('"')) {
substring(1, length - 1).replace("\\\"", "\"").replace("\\\\", "\\")
} else {
this
private fun unescapeString(s: String): String {
val sb = StringBuilder(s.length)
var i = 0
while (i < s.length) {
var ch = s[i]
if (ch == '\\' && i + 1 < s.length) {
ch = s[++i]
when (ch) {
'b' -> sb.append('\b')
'f' -> sb.append('\u000C')
'n' -> sb.append('\n')
'r' -> sb.append('\r')
't' -> sb.append('\t')
'u' -> {
if (i + 4 < s.length) {
val hex = s.substring(i + 1, i + 5)
sb.append(hex.toInt(16).toChar())
i += 4
}
}

else -> sb.append(ch)
}
} else {
sb.append(ch)
}
i++
}
return sb.toString()
}
}
Loading

0 comments on commit 4b52011

Please sign in to comment.