Skip to content

Commit bf54fac

Browse files
authored
Merge pull request #34 from hossain-khan/copilot/fix-33
Add external JSON5 library for three-way benchmark comparison
2 parents d1e1bd2 + dd9326a commit bf54fac

File tree

6 files changed

+171
-52
lines changed

6 files changed

+171
-52
lines changed

benchmark/README.md

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
# JSON5 vs JSON Performance Benchmark
1+
# JSON5 Performance Benchmark - Three-way Comparison
22

3-
This module provides benchmarking tools to compare the performance of JSON5 serialization/deserialization against standard JSON using kotlinx.serialization.
3+
This module provides benchmarking tools to compare the performance of JSON5 serialization/deserialization across three different implementations:
4+
5+
1. **JSON5** (this project) - Uses kotlinx.serialization
6+
2. **JSON** (kotlinx.serialization) - Standard JSON baseline
7+
3. **External-JSON5** (at.syntaxerror.json5:2.1.0) - External JSON5 library
48

59
## Running the Benchmark
610

@@ -27,9 +31,9 @@ The benchmark generates two types of reports:
2731

2832
### Summary Report
2933
- File: `benchmark_summary_YYYY-MM-DD_HH-mm-ss.txt`
30-
- Contains human-readable performance comparisons
31-
- Shows which format is faster and by how much
32-
- Includes overall statistics
34+
- Contains human-readable performance comparisons across all three libraries
35+
- Shows relative performance comparisons between each pair of libraries
36+
- Includes overall statistics and rankings
3337

3438
## Test Data Types
3539

@@ -59,39 +63,37 @@ To run the benchmark module tests:
5963

6064
## Sample Results
6165

62-
Based on typical runs, JSON standard library generally performs 2-6x faster than JSON5 for both serialization and deserialization, with the performance gap being larger for more complex data structures.
66+
Based on typical runs across the three libraries:
67+
68+
- **JSON** (kotlinx.serialization) is consistently the fastest
69+
- **External-JSON5** performs better than this project's JSON5 implementation
70+
- **JSON5** (this project) offers kotlinx.serialization integration but with slower performance
6371

6472
Example output:
6573
```
66-
SimplePerson Serialization: JSON5=0.027ms, JSON=0.013ms
67-
ComplexPerson Serialization: JSON5=0.083ms, JSON=0.015ms
68-
Company Serialization: JSON5=0.200ms, JSON=0.032ms
74+
SimplePerson Serialization: JSON5=0.028ms, JSON=0.010ms, External-JSON5=0.011ms
75+
ComplexPerson Serialization: JSON5=0.073ms, JSON=0.018ms, External-JSON5=0.018ms
76+
Company Serialization: JSON5=0.163ms, JSON=0.031ms, External-JSON5=0.054ms
6977
```
7078

7179
### Benchmark Result Snapshot
7280

73-
| Case | Type | JSON5 Avg (ms) | JSON Avg (ms) | Speedup (JSON) |
74-
| ------------------- | --------------- | -------------- | ------------- | -------------- |
75-
| SimplePerson | Serialization | 0.056 | 0.020 | 2.77× |
76-
| SimplePerson | Deserialization | 0.064 | 0.022 | 2.93× |
77-
| ComplexPerson | Serialization | 0.089 | 0.019 | 4.59× |
78-
| ComplexPerson | Deserialization | 0.113 | 0.030 | 3.76× |
79-
| Company | Serialization | 0.226 | 0.059 | 3.81× |
80-
| Company | Deserialization | 0.254 | 0.090 | 2.83× |
81-
| NumberTypes | Serialization | 0.032 | 0.003 | 9.43× |
82-
| NumberTypes | Deserialization | 0.021 | 0.003 | 6.60× |
83-
| CollectionTypes | Serialization | 0.067 | 0.009 | 7.41× |
84-
| CollectionTypes | Deserialization | 0.059 | 0.025 | 2.37× |
85-
| SimplePersonList100 | Serialization | 0.153 | 0.042 | 3.64× |
86-
| SimplePersonList100 | Deserialization | 0.234 | 0.039 | 5.99× |
87-
| ComplexPersonList50 | Serialization | 0.388 | 0.059 | 6.59× |
88-
| ComplexPersonList50 | Deserialization | 0.452 | 0.089 | 5.09× |
89-
90-
**Overall Average Time**
91-
92-
* JSON5: **0.158 ms**
93-
* JSON: **0.036 ms**
94-
* 🔥 Overall: **KotlinX JSON is 4.33× faster than JSON5**
95-
96-
97-
<img width="800" alt="benchmark bar chart" src="https://github.com/user-attachments/assets/bcf44217-827b-49a1-a18b-4a0f7fedc103" />
81+
| Case | Type | JSON5 (ms) | JSON (ms) | External-JSON5 (ms) | Performance Ranking |
82+
| ------------------- | --------------- | ---------- | --------- | ------------------- | ------------------- |
83+
| SimplePerson | Serialization | 0.028 | 0.010 | 0.011 | JSON > Ext-JSON5 > JSON5 |
84+
| SimplePerson | Deserialization | 0.049 | 0.014 | 0.017 | JSON > Ext-JSON5 > JSON5 |
85+
| ComplexPerson | Serialization | 0.073 | 0.018 | 0.018 | JSON ≈ Ext-JSON5 > JSON5 |
86+
| ComplexPerson | Deserialization | 0.101 | 0.021 | 0.020 | Ext-JSON5 > JSON > JSON5 |
87+
| Company | Serialization | 0.163 | 0.031 | 0.054 | JSON > Ext-JSON5 > JSON5 |
88+
| Company | Deserialization | 0.200 | 0.081 | 0.117 | JSON > Ext-JSON5 > JSON5 |
89+
90+
**Overall Performance Comparison:**
91+
- **JSON** is **3.90×** faster than **JSON5** and **2.75×** faster than **External-JSON5**
92+
- **External-JSON5** is **1.42×** faster than **JSON5**
93+
94+
## Key Insights
95+
96+
- **kotlinx.serialization JSON** remains the performance leader
97+
- **External JSON5 library** provides a good balance of JSON5 features with reasonable performance
98+
- **This project's JSON5** offers seamless kotlinx.serialization integration but at a performance cost
99+
- Choose based on your priorities: performance (JSON), JSON5 features with good performance (External-JSON5), or kotlinx.serialization integration (this project)

benchmark/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ dependencies {
1111

1212
// kotlinx.serialization for JSON comparison
1313
implementation(libs.bundles.kotlinxEcosystem)
14+
15+
// External JSON5 library for comparison
16+
implementation("at.syntaxerror:json5:2.1.0")
1417

1518
// Test dependencies
1619
testImplementation(kotlin("test"))

benchmark/src/main/kotlin/dev/hossain/json5kt/benchmark/BenchmarkRunner.kt

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,35 @@ object BenchmarkRunner {
117117

118118
val json5Result = resultList.find { it.format == "JSON5" }
119119
val jsonResult = resultList.find { it.format == "JSON" }
120+
val externalJson5Result = resultList.find { it.format == "External-JSON5" }
120121

121-
if (json5Result != null && jsonResult != null) {
122-
appendLine(" JSON5: ${String.format("%.3f", json5Result.averageTimeMillis)} ms avg")
123-
appendLine(" JSON: ${String.format("%.3f", jsonResult.averageTimeMillis)} ms avg")
122+
if (json5Result != null && jsonResult != null && externalJson5Result != null) {
123+
appendLine(" JSON5: ${String.format("%.3f", json5Result.averageTimeMillis)} ms avg")
124+
appendLine(" JSON: ${String.format("%.3f", jsonResult.averageTimeMillis)} ms avg")
125+
appendLine(" External-JSON5: ${String.format("%.3f", externalJson5Result.averageTimeMillis)} ms avg")
124126

125-
val speedup = json5Result.averageTimeMillis / jsonResult.averageTimeMillis
126-
if (speedup > 1.0) {
127-
appendLine(" → JSON is ${String.format("%.2f", speedup)}x faster than JSON5")
127+
// Compare all libraries
128+
val json5VsJson = json5Result.averageTimeMillis / jsonResult.averageTimeMillis
129+
val externalVsJson = externalJson5Result.averageTimeMillis / jsonResult.averageTimeMillis
130+
val json5VsExternal = json5Result.averageTimeMillis / externalJson5Result.averageTimeMillis
131+
132+
appendLine(" Comparisons:")
133+
if (json5VsJson > 1.0) {
134+
appendLine(" → JSON is ${String.format("%.2f", json5VsJson)}x faster than JSON5")
135+
} else {
136+
appendLine(" → JSON5 is ${String.format("%.2f", 1.0 / json5VsJson)}x faster than JSON")
137+
}
138+
139+
if (externalVsJson > 1.0) {
140+
appendLine(" → JSON is ${String.format("%.2f", externalVsJson)}x faster than External-JSON5")
141+
} else {
142+
appendLine(" → External-JSON5 is ${String.format("%.2f", 1.0 / externalVsJson)}x faster than JSON")
143+
}
144+
145+
if (json5VsExternal > 1.0) {
146+
appendLine(" → External-JSON5 is ${String.format("%.2f", json5VsExternal)}x faster than JSON5")
128147
} else {
129-
appendLine(" → JSON5 is ${String.format("%.2f", 1.0 / speedup)}x faster than JSON")
148+
appendLine(" → JSON5 is ${String.format("%.2f", 1.0 / json5VsExternal)}x faster than External-JSON5")
130149
}
131150
}
132151
appendLine()
@@ -136,18 +155,37 @@ object BenchmarkRunner {
136155
appendLine("Overall Statistics:")
137156
val json5Results = results.filter { it.format == "JSON5" }
138157
val jsonResults = results.filter { it.format == "JSON" }
158+
val externalJson5Results = results.filter { it.format == "External-JSON5" }
139159

140160
val avgJson5Time = json5Results.map { it.averageTimeMillis }.average()
141161
val avgJsonTime = jsonResults.map { it.averageTimeMillis }.average()
162+
val avgExternalJson5Time = externalJson5Results.map { it.averageTimeMillis }.average()
142163

143-
appendLine("Average JSON5 time: ${String.format("%.3f", avgJson5Time)} ms")
144-
appendLine("Average JSON time: ${String.format("%.3f", avgJsonTime)} ms")
164+
appendLine("Average JSON5 time: ${String.format("%.3f", avgJson5Time)} ms")
165+
appendLine("Average JSON time: ${String.format("%.3f", avgJsonTime)} ms")
166+
appendLine("Average External-JSON5 time: ${String.format("%.3f", avgExternalJson5Time)} ms")
167+
168+
val json5VsJson = avgJson5Time / avgJsonTime
169+
val externalVsJson = avgExternalJson5Time / avgJsonTime
170+
val json5VsExternal = avgJson5Time / avgExternalJson5Time
171+
172+
appendLine("\nOverall Comparisons:")
173+
if (json5VsJson > 1.0) {
174+
appendLine(" → JSON is ${String.format("%.2f", json5VsJson)}x faster than JSON5")
175+
} else {
176+
appendLine(" → JSON5 is ${String.format("%.2f", 1.0 / json5VsJson)}x faster than JSON")
177+
}
178+
179+
if (externalVsJson > 1.0) {
180+
appendLine(" → JSON is ${String.format("%.2f", externalVsJson)}x faster than External-JSON5")
181+
} else {
182+
appendLine(" → External-JSON5 is ${String.format("%.2f", 1.0 / externalVsJson)}x faster than JSON")
183+
}
145184

146-
val overallSpeedup = avgJson5Time / avgJsonTime
147-
if (overallSpeedup > 1.0) {
148-
appendLine("Overall: JSON is ${String.format("%.2f", overallSpeedup)}x faster than JSON5")
185+
if (json5VsExternal > 1.0) {
186+
appendLine(" → External-JSON5 is ${String.format("%.2f", json5VsExternal)}x faster than JSON5")
149187
} else {
150-
appendLine("Overall: JSON5 is ${String.format("%.2f", 1.0 / overallSpeedup)}x faster than JSON")
188+
appendLine("JSON5 is ${String.format("%.2f", 1.0 / json5VsExternal)}x faster than External-JSON5")
151189
}
152190
}
153191

@@ -161,9 +199,10 @@ object BenchmarkRunner {
161199
val (dataType, operation) = key.split("_")
162200
val json5Result = resultList.find { it.format == "JSON5" }
163201
val jsonResult = resultList.find { it.format == "JSON" }
202+
val externalJson5Result = resultList.find { it.format == "External-JSON5" }
164203

165-
if (json5Result != null && jsonResult != null) {
166-
println("$dataType $operation: JSON5=${String.format("%.3f", json5Result.averageTimeMillis)}ms, JSON=${String.format("%.3f", jsonResult.averageTimeMillis)}ms")
204+
if (json5Result != null && jsonResult != null && externalJson5Result != null) {
205+
println("$dataType $operation: JSON5=${String.format("%.3f", json5Result.averageTimeMillis)}ms, JSON=${String.format("%.3f", jsonResult.averageTimeMillis)}ms, External-JSON5=${String.format("%.3f", externalJson5Result.averageTimeMillis)}ms")
167206
}
168207
}
169208
}

benchmark/src/main/kotlin/dev/hossain/json5kt/benchmark/SerializationBenchmark.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import kotlinx.serialization.encodeToString
66
import kotlinx.serialization.decodeFromString
77
import kotlinx.serialization.serializer
88
import kotlin.system.measureNanoTime
9+
import at.syntaxerror.json5.*
910

1011
/**
1112
* Benchmark class for comparing JSON5 and standard JSON performance.
@@ -38,6 +39,35 @@ class SerializationBenchmark {
3839
}
3940
}
4041

42+
/**
43+
* Converts a kotlinx.serialization object to a plain object that can be used with the external JSON5 library.
44+
* This is done by serializing to JSON with kotlinx.serialization and then parsing with external JSON5 library.
45+
*/
46+
private fun <T> convertToPlainObject(data: T, serializer: kotlinx.serialization.SerializationStrategy<T>): Any? {
47+
val jsonString = json.encodeToString(serializer, data)
48+
val parser = JSONParser(jsonString)
49+
return parser.nextValue()
50+
}
51+
52+
/**
53+
* Converts a JSON5 string to a plain object using the external JSON5 library.
54+
*/
55+
private fun parseWithExternalLibrary(json5String: String): Any? {
56+
val parser = JSONParser(json5String)
57+
return parser.nextValue()
58+
}
59+
60+
/**
61+
* Converts a plain object to JSON5 string using the external JSON5 library.
62+
*/
63+
private fun stringifyWithExternalLibrary(obj: Any?): String {
64+
return when (obj) {
65+
is JSONObject -> JSONStringify.toString(obj, 0)
66+
is JSONArray -> JSONStringify.toString(obj, 0)
67+
else -> obj?.toString() ?: "null"
68+
}
69+
}
70+
4171
/**
4272
* Runs serialization benchmark for the given data and serializer.
4373
*/
@@ -89,6 +119,27 @@ class SerializationBenchmark {
89119
)
90120
)
91121

122+
// Benchmark external JSON5 library serialization
123+
val externalJson5Object = convertToPlainObject(data, serializer)
124+
val externalJson5SerializationTime = measureNanoTime {
125+
repeat(iterations) {
126+
stringifyWithExternalLibrary(externalJson5Object)
127+
}
128+
}
129+
130+
results.add(
131+
BenchmarkResult(
132+
operation = "Serialization",
133+
dataType = dataTypeName,
134+
format = "External-JSON5",
135+
iterations = iterations,
136+
totalTimeNanos = externalJson5SerializationTime,
137+
averageTimeNanos = externalJson5SerializationTime / iterations,
138+
averageTimeMicros = (externalJson5SerializationTime / iterations) / 1000.0,
139+
averageTimeMillis = (externalJson5SerializationTime / iterations) / 1_000_000.0
140+
)
141+
)
142+
92143
return results
93144
}
94145

@@ -144,6 +195,26 @@ class SerializationBenchmark {
144195
)
145196
)
146197

198+
// Benchmark external JSON5 library deserialization
199+
val externalJson5DeserializationTime = measureNanoTime {
200+
repeat(iterations) {
201+
parseWithExternalLibrary(json5String)
202+
}
203+
}
204+
205+
results.add(
206+
BenchmarkResult(
207+
operation = "Deserialization",
208+
dataType = dataTypeName,
209+
format = "External-JSON5",
210+
iterations = iterations,
211+
totalTimeNanos = externalJson5DeserializationTime,
212+
averageTimeNanos = externalJson5DeserializationTime / iterations,
213+
averageTimeMicros = (externalJson5DeserializationTime / iterations) / 1000.0,
214+
averageTimeMillis = (externalJson5DeserializationTime / iterations) / 1_000_000.0
215+
)
216+
)
217+
147218
return results
148219
}
149220

benchmark/src/test/kotlin/dev/hossain/json5kt/benchmark/BenchmarkTest.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,22 @@ class BenchmarkTest {
4545
assertNotNull(results)
4646
assertTrue(results.isNotEmpty())
4747

48-
// Should have 4 results: JSON5 and JSON for both serialization and deserialization
49-
assertEquals(4, results.size)
48+
// Should have 6 results: JSON5, JSON, and External-JSON5 for both serialization and deserialization
49+
assertEquals(6, results.size)
5050

5151
val json5SerializationResult = results.find { it.format == "JSON5" && it.operation == "Serialization" }
5252
val jsonSerializationResult = results.find { it.format == "JSON" && it.operation == "Serialization" }
53+
val externalJson5SerializationResult = results.find { it.format == "External-JSON5" && it.operation == "Serialization" }
5354
val json5DeserializationResult = results.find { it.format == "JSON5" && it.operation == "Deserialization" }
5455
val jsonDeserializationResult = results.find { it.format == "JSON" && it.operation == "Deserialization" }
56+
val externalJson5DeserializationResult = results.find { it.format == "External-JSON5" && it.operation == "Deserialization" }
5557

5658
assertNotNull(json5SerializationResult)
5759
assertNotNull(jsonSerializationResult)
60+
assertNotNull(externalJson5SerializationResult)
5861
assertNotNull(json5DeserializationResult)
5962
assertNotNull(jsonDeserializationResult)
63+
assertNotNull(externalJson5DeserializationResult)
6064

6165
// Verify basic properties
6266
assertEquals("TestPerson", json5SerializationResult!!.dataType)

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
kotlin {
9-
jvmToolchain(21) // Changed from 23 to 21
9+
jvmToolchain(17) // Use Java 17 to match available JVM
1010
}
1111

1212
dependencies {

0 commit comments

Comments
 (0)