diff --git a/.github/DEVELOPMENT.md b/.github/DEVELOPMENT.md index 9651643..8a0e783 100644 --- a/.github/DEVELOPMENT.md +++ b/.github/DEVELOPMENT.md @@ -141,9 +141,10 @@ lib/src/main/kotlin/dev/hossain/json5kt/ **Key Components:** 1. **JSON5** - Main API facade - - `parseToJsonElement()` - Parse JSON5 to JsonElement - - `encodeToString()` - Serialize objects to JSON5 - - `decodeFromString()` - Deserialize JSON5 to objects + - `parse()` - Parse JSON5 to JSON5Value objects + - `stringify()` - Convert Kotlin objects to JSON5 strings + - `encodeToString()` - Serialize objects to JSON5 using kotlinx.serialization + - `decodeFromString()` - Deserialize JSON5 to objects using kotlinx.serialization 2. **JSON5Parser** - Core parsing engine - Handles JSON5 syntax: comments, unquoted keys, trailing commas @@ -204,9 +205,9 @@ open lib/build/reports/kover/html/index.html 1. **Parsing Errors** ```kotlin try { - val result = JSON5.parseToJsonElement(json5Text) + val result = JSON5.parse(json5Text) } catch (e: JSON5Exception) { - println("Parse error at position ${e.position}: ${e.message}") + println("Parse error at line ${e.lineNumber}, column ${e.columnNumber}: ${e.message}") } ``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f6f110d..8e99058 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,5 +14,24 @@ - [ ] Code refactoring - [ ] Test improvements +## Testing + + + +- [ ] Tests pass locally with my changes +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes + +## Checklist + + + +- [ ] My code follows the code style of this project +- [ ] I have run `./gradlew formatKotlin` to format my code +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works diff --git a/.gitignore b/.gitignore index 2642bed..3d26dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,18 @@ build/ # Benchmark result files benchmark_results_*.csv benchmark_summary_*.txt + +# Editor temporary files +*.swp +*.swo +*~ +.vscode/settings.json + +# OS generated files +Thumbs.db +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd489bb..56981de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,10 +135,24 @@ class JSON5ParserTest { """.trimIndent() // When - val result = JSON5.parseToJsonElement(json5) + val result = JSON5.parse(json5) // Then - // assertions... + assertThat(result).isInstanceOf(JSON5Value.Object::class.java) + val obj = result as JSON5Value.Object + assertThat((obj.value["name"] as JSON5Value.String).value).isEqualTo("test") + assertThat((obj.value["value"] as JSON5Value.Number.Integer).value).isEqualTo(42) + } + + @Test + fun `should throw exception for invalid JSON5`() { + // Given + val invalidJson5 = "{ invalid: syntax }" + + // When & Then + assertThrows { + JSON5.parse(invalidJson5) + } } } ``` diff --git a/README.md b/README.md index f42a7aa..130a17b 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,15 @@ To access GitHub Packages, you need to authenticate with GitHub. You can do this You can generate a personal access token at [GitHub > Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) with `read:packages` permission. +> **Note**: Make sure to use the **classic** personal access token format, not the new fine-grained tokens, as GitHub Packages currently requires classic tokens. + ## Usage ### Basic Parsing and Stringifying ```kotlin import dev.hossain.json5kt.JSON5 +import dev.hossain.json5kt.JSON5Exception // Parse JSON5 to strongly-typed JSON5Value objects val json5 = """ @@ -112,20 +115,24 @@ val json5 = """ } """ -val parsed = JSON5.parse(json5) -// Returns: JSON5Value.Object - -// Access values in a type-safe way -when (parsed) { - is JSON5Value.Object -> { - val name = parsed.value["name"] as? JSON5Value.String - val version = parsed.value["version"] as? JSON5Value.Number.Integer - val features = parsed.value["features"] as? JSON5Value.Array - - println("App name: ${name?.value}") // "MyApp" - println("Version: ${version?.value}") // 2 - println("Features: ${features?.value?.map { (it as JSON5Value.String).value }}") // ["auth", "analytics"] +try { + val parsed = JSON5.parse(json5) + // Returns: JSON5Value.Object + + // Access values in a type-safe way + when (parsed) { + is JSON5Value.Object -> { + val name = parsed.value["name"] as? JSON5Value.String + val version = parsed.value["version"] as? JSON5Value.Number.Integer + val features = parsed.value["features"] as? JSON5Value.Array + + println("App name: ${name?.value}") // "MyApp" + println("Version: ${version?.value}") // 2 + println("Features: ${features?.value?.map { (it as JSON5Value.String).value }}") // ["auth", "analytics"] + } } +} catch (e: JSON5Exception) { + println("Failed to parse JSON5: ${e.message}") } // Stringify Kotlin objects to JSON5 @@ -142,7 +149,9 @@ val json5String = JSON5.stringify(data) ```kotlin import dev.hossain.json5kt.JSON5 +import dev.hossain.json5kt.JSON5Exception import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException @Serializable data class Config( @@ -160,28 +169,35 @@ val config = Config( settings = mapOf("theme" to "dark", "lang" to "en") ) -val json5 = JSON5.encodeToString(Config.serializer(), config) -// Result: {appName:'MyApp',version:2,features:['auth','analytics'],settings:{theme:'dark',lang:'en'}} - -// Deserialize from JSON5 (with comments and formatting) -val json5WithComments = """ -{ - // Application configuration - appName: 'MyApp', - version: 2, // current version - features: [ - 'auth', - 'analytics', // trailing comma OK - ], - settings: { - theme: 'dark', - lang: 'en', +try { + val json5 = JSON5.encodeToString(Config.serializer(), config) + // Result: {appName:'MyApp',version:2,features:['auth','analytics'],settings:{theme:'dark',lang:'en'}} + + // Deserialize from JSON5 (with comments and formatting) + val json5WithComments = """ + { + // Application configuration + appName: 'MyApp', + version: 2, // current version + features: [ + 'auth', + 'analytics', // trailing comma OK + ], + settings: { + theme: 'dark', + lang: 'en', + } } + """ + + val decoded = JSON5.decodeFromString(Config.serializer(), json5WithComments) + // Returns: Config instance + println("Loaded config: $decoded") +} catch (e: JSON5Exception) { + println("JSON5 parsing error: ${e.message}") +} catch (e: SerializationException) { + println("Serialization error: ${e.message}") } -""" - -val decoded = JSON5.decodeFromString(Config.serializer(), json5WithComments) -// Returns: Config instance ``` ### Advanced Features @@ -243,12 +259,43 @@ when (complex) { This project uses [Gradle](https://gradle.org/) with Java 21: ```bash -./gradlew build # Build the library -./gradlew test # Run tests -./gradlew check # Run all checks including tests -./gradlew :benchmark:run # Runs the benchmark +./gradlew build # Build all modules +./gradlew test # Run tests +./gradlew check # Run all checks including tests, linting, and coverage +./gradlew formatKotlin # Format code with ktlint +./gradlew :benchmark:run # Run performance benchmarks +./gradlew koverHtmlReport # Generate code coverage report ``` +### Requirements + +- **Java 21** or later +- **Git** (for submodules containing JSON5 specification) + +## Troubleshooting + +### Common Issues + +**Build fails with "Build requires Java 21"** +- Ensure you have Java 21 installed: `java -version` +- Set `JAVA_HOME` environment variable to Java 21 installation + +**Gradle daemon issues** +- Stop the daemon: `./gradlew --stop` +- Try again with: `./gradlew build --no-daemon` + +**GitHub Packages authentication issues** +- Verify your GitHub username and personal access token +- Ensure the token has `read:packages` permission +- Use classic tokens, not fine-grained tokens + +**Submodule not initialized** +```bash +git submodule update --init --recursive +``` + +For more detailed troubleshooting, see our [Development Guide](.github/DEVELOPMENT.md). + ## Contributing We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on: diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..9c7358e --- /dev/null +++ b/app/README.md @@ -0,0 +1,50 @@ +# JSON5 Demo Application + +This module contains a demonstration application that showcases the json5-kotlin library's features and capabilities. + +## What it demonstrates + +- **Basic JSON5 parsing and stringification** +- **kotlinx.serialization integration** with custom data classes +- **Advanced JSON5 features** including: + - Comments in JSON5 files + - Trailing commas + - Unquoted keys + - Different number formats (hex, scientific notation, special values) + - Multi-line strings + - Various string quote styles +- **Real-world JSON5 file processing** from the `src/main/resources` directory +- **Error handling** and validation examples + +## Running the demo + +To run the demonstration application: + +```bash +./gradlew :app:run +``` + +This will execute various examples showing: +1. Employee serialization/deserialization with kotlinx.serialization +2. All code examples from the README.md validation +3. Processing of sample JSON5 files with different features + +## Sample JSON5 files + +The `src/main/resources` directory contains example JSON5 files demonstrating: + +- `simple-object.json5` - Basic object with comments and mixed quote styles +- `array-example.json5` - Arrays with different data types and trailing commas +- `numeric-formats.json5` - Various number formats supported by JSON5 +- `string-and-identifiers.json5` - String escape sequences and special identifiers +- `root-string.json5` - Root-level string value (valid in JSON5) + +## Use as a testing ground + +This app module serves as: +- A **validation tool** to ensure the library works correctly +- A **reference implementation** showing best practices +- A **testing ground** for experimenting with JSON5 features +- **Living documentation** of the library's capabilities + +The code in this module is designed to be readable and educational, providing practical examples of how to use the json5-kotlin library in real applications. \ No newline at end of file diff --git a/benchmark/README.md b/benchmark/README.md index 42a81c6..8f12f4c 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -68,13 +68,18 @@ To run the benchmark module tests: ./gradlew :benchmark:test ``` -## Sample Results +## Performance Expectations Based on typical runs across the three libraries: -- **JSON** (kotlinx.serialization) is consistently the fastest -- **External-JSON5** performs better than this project's JSON5 implementation -- **JSON5** (this project) offers kotlinx.serialization integration but with slower performance +- **JSON** (kotlinx.serialization) is consistently the fastest (~4-5x faster than JSON5 implementations) +- **External-JSON5** (at.syntaxerror.json5) provides good JSON5 performance (~2x faster than this project) +- **JSON5** (this project) prioritizes kotlinx.serialization integration over raw performance + +This performance trade-off is expected because: +- JSON5 parsing requires additional processing for comments, flexible syntax, and extended number formats +- This project integrates with kotlinx.serialization which adds serialization overhead +- External JSON5 libraries may use optimized parsing techniques not compatible with kotlinx.serialization Example output: ``` @@ -102,9 +107,27 @@ Company Serialization: JSON5=0.233ms, JSON=0.056ms, External-JSON5=0.073ms - **JSON** is **4.45×** faster than **JSON5** and **2.51×** faster than **External-JSON5** - **External-JSON5** is **1.77×** faster than **JSON5** +## Interpreting Results + +When choosing between implementations, consider: + +- **Use JSON** (kotlinx.serialization) if you need maximum performance and don't require JSON5 features +- **Use External-JSON5** if you need JSON5 features with good performance and don't need kotlinx.serialization integration +- **Use this JSON5 implementation** if you need seamless kotlinx.serialization integration with JSON5 features + +The performance differences are most noticeable with: +- Large files (>1MB) +- High-frequency operations (>1000 operations/second) +- Resource-constrained environments + +For typical configuration files and small-to-medium data, the performance difference may not be significant compared to the development benefits of kotlinx.serialization integration. + ## Key Insights -- **kotlinx.serialization JSON** remains the performance leader -- **External JSON5 library** provides a good balance of JSON5 features with reasonable performance -- **This project's JSON5** offers seamless kotlinx.serialization integration but at a performance cost +- **kotlinx.serialization JSON** remains the performance leader for standard JSON operations +- **External JSON5 library** provides a good balance of JSON5 features with reasonable performance +- **This project's JSON5** offers seamless kotlinx.serialization integration but with a performance trade-off +- Performance differences are most significant in high-throughput scenarios - Choose based on your priorities: performance (JSON), JSON5 features with good performance (External-JSON5), or kotlinx.serialization integration (this project) + +> **Note**: These benchmarks focus on raw parsing/serialization performance. In real applications, the convenience and type safety of kotlinx.serialization integration may outweigh the performance difference for many use cases. diff --git a/settings.gradle.kts b/settings.gradle.kts index 3c39ca9..a64f31e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,7 +18,7 @@ plugins { // Include the `app`, `lib` and `benchmark` subprojects in the build. // If there are changes in only one of the projects, Gradle will rebuild only the one that has changed. -// Learn more about structuring projects with Gradle - https://docs.gradle.org/8.7/userguide/multi_project_builds.html +// Learn more about structuring projects with Gradle - https://docs.gradle.org/8.14.2/userguide/multi_project_builds.html include(":app") include(":lib") include(":benchmark")