added support for jsonc config

This commit is contained in:
geoffsee
2025-06-30 11:30:09 -04:00
parent 08fb45dbaf
commit 9c4d5651c3
4 changed files with 307 additions and 14 deletions

40
config.jsonc Normal file
View File

@@ -0,0 +1,40 @@
{
// OSM Maker Configuration File
// This file demonstrates JSONC (JSON with Comments) support
"osmData": {
"useLocalExtract": false, // Set to true to use local OSM file
"localFilePath": "virginia.osm.pbf",
/* Bounding box configuration
* Defines the geographic area to process
*/
"boundingBox": {
"south": 37.115, // Southern latitude boundary
"west": -76.396, // Western longitude boundary
"north": 37.139, // Northern latitude boundary
"east": -76.345, // Eastern longitude boundary
"description": "Poquoson, VA" // Human-readable description
},
"overpassTimeout": 25 // Timeout for Overpass API queries in seconds
},
// Projection settings for coordinate transformation
"projection": {
"origin": {
"latitude": 37.120907, // Center point latitude
"longitude": -76.333694 // Center point longitude
}
},
/* Output configuration
* Controls how the final 3D model is generated and handled
*/
"output": {
"fileName": "municipality.glb", // Output file name
"autoOpen": true // Whether to automatically open the generated file
}
// End of configuration
}

View File

@@ -1,6 +1,7 @@
package org.example
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class Config(
@@ -42,3 +43,86 @@ data class OutputConfig(
val fileName: String,
val autoOpen: Boolean
)
/**
* Parses JSONC (JSON with Comments) content by removing comments and parsing as regular JSON
*/
fun parseJsonc(jsoncContent: String): Config {
val cleanedJson = removeJsoncComments(jsoncContent)
return Json.decodeFromString<Config>(cleanedJson)
}
/**
* Removes both single-line (//) and multi-line (/* */) comments from JSONC content
*/
private fun removeJsoncComments(content: String): String {
val result = StringBuilder()
var i = 0
var inString = false
var escaped = false
while (i < content.length) {
val char = content[i]
when {
// Handle escape sequences in strings
escaped -> {
result.append(char)
escaped = false
i++
}
// Handle string boundaries
char == '"' && !escaped -> {
inString = !inString
result.append(char)
i++
}
// Handle escape character
char == '\\' && inString -> {
escaped = true
result.append(char)
i++
}
// Skip comments when not in string
!inString && char == '/' && i + 1 < content.length -> {
when (content[i + 1]) {
// Single-line comment
'/' -> {
// Skip until end of line
i += 2
while (i < content.length && content[i] != '\n') {
i++
}
// Keep the newline for proper formatting
if (i < content.length) {
result.append('\n')
i++
}
}
// Multi-line comment
'*' -> {
// Skip until */
i += 2
while (i + 1 < content.length && !(content[i] == '*' && content[i + 1] == '/')) {
i++
}
if (i + 1 < content.length) {
i += 2 // Skip the closing */
}
}
else -> {
result.append(char)
i++
}
}
}
// Regular character
else -> {
result.append(char)
i++
}
}
}
return result.toString()
}

View File

@@ -4,6 +4,7 @@ import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import java.io.File
class ConfigTest {
@@ -52,4 +53,155 @@ class ConfigTest {
assertEquals("municipality.glb", config.output.fileName)
assertEquals(true, config.output.autoOpen)
}
@Test
fun testJsoncWithSingleLineComments() {
val jsoncString = """
{
// This is a comment about OSM data configuration
"osmData": {
"useLocalExtract": false, // Use remote data instead
"localFilePath": "virginia.osm.pbf",
"boundingBox": {
"south": 37.115,
"west": -76.396,
"north": 37.139,
"east": -76.345,
"description": "Poquoson, VA" // A small city in Virginia
},
"overpassTimeout": 25 // Timeout in seconds
},
"projection": {
"origin": {
"latitude": 37.120907, // Center latitude
"longitude": -76.333694 // Center longitude
}
},
"output": {
"fileName": "municipality.glb", // Output file name
"autoOpen": true // Automatically open the file
}
}
""".trimIndent()
val config = parseJsonc(jsoncString)
assertNotNull(config)
assertEquals(false, config.osmData.useLocalExtract)
assertEquals("virginia.osm.pbf", config.osmData.localFilePath)
assertEquals("Poquoson, VA", config.osmData.boundingBox.description)
assertEquals(25, config.osmData.overpassTimeout)
assertEquals("municipality.glb", config.output.fileName)
assertEquals(true, config.output.autoOpen)
}
@Test
fun testJsoncWithMultiLineComments() {
val jsoncString = """
{
/*
* OSM Data Configuration
* This section configures how OSM data is obtained
*/
"osmData": {
"useLocalExtract": false,
"localFilePath": "virginia.osm.pbf",
"boundingBox": {
"south": 37.115,
"west": -76.396,
"north": 37.139,
"east": -76.345,
"description": "Poquoson, VA"
},
"overpassTimeout": 25
},
/* Projection settings */
"projection": {
"origin": {
"latitude": 37.120907,
"longitude": -76.333694
}
},
/*
* Output configuration
* Controls how the final file is generated
*/
"output": {
"fileName": "municipality.glb",
"autoOpen": true
}
}
""".trimIndent()
val config = parseJsonc(jsoncString)
assertNotNull(config)
assertEquals(false, config.osmData.useLocalExtract)
assertEquals("Poquoson, VA", config.osmData.boundingBox.description)
assertEquals("municipality.glb", config.output.fileName)
}
@Test
fun testJsoncWithMixedComments() {
val jsoncString = """
{
// Single line comment at the top
"osmData": {
/* Multi-line comment
about local extract */
"useLocalExtract": false, // Inline comment
"localFilePath": "virginia.osm.pbf",
"boundingBox": {
"south": 37.115, // Southern boundary
"west": -76.396,
"north": 37.139,
"east": -76.345,
"description": "Poquoson, VA"
},
"overpassTimeout": 25
},
"projection": {
"origin": {
"latitude": 37.120907,
"longitude": -76.333694
}
},
"output": {
"fileName": "municipality.glb",
"autoOpen": true
}
// Final comment
}
""".trimIndent()
val config = parseJsonc(jsoncString)
assertNotNull(config)
assertEquals(false, config.osmData.useLocalExtract)
assertEquals("Poquoson, VA", config.osmData.boundingBox.description)
}
@Test
fun testLoadActualJsoncFile() {
val configFile = File("config.jsonc")
if (!configFile.exists()) {
println("Skipping test - config.jsonc file not found")
return
}
val configText = configFile.readText()
val config = parseJsonc(configText)
assertNotNull(config)
assertEquals(false, config.osmData.useLocalExtract)
assertEquals("virginia.osm.pbf", config.osmData.localFilePath)
assertEquals("Poquoson, VA", config.osmData.boundingBox.description)
assertEquals(25, config.osmData.overpassTimeout)
assertEquals(37.120907, config.projection.origin.latitude)
assertEquals(-76.333694, config.projection.origin.longitude)
assertEquals("municipality.glb", config.output.fileName)
assertEquals(true, config.output.autoOpen)
println("✅ Successfully loaded and parsed config.jsonc file with comments!")
}
}

View File

@@ -10,22 +10,39 @@ fun main() {
LOAD AND DEMONSTRATE JSON CONFIGURATION
---------------------------------------------------------------- */
val configFile = File("config.json")
if (!configFile.exists()) {
println("Error: config.json not found in current directory")
println("Please ensure config.json exists in the working directory")
return
// Try to find configuration file (prefer .jsonc, fallback to .json)
val configFile = when {
File("config.jsonc").exists() -> File("config.jsonc")
File("config.json").exists() -> File("config.json")
else -> {
println("Error: No configuration file found")
println("Please ensure either config.jsonc or config.json exists in the working directory")
return
}
}
val config = try {
val configText = configFile.readText()
Json.decodeFromString<Config>(configText)
when (configFile.extension.lowercase()) {
"jsonc" -> {
println("📄 Loading JSONC configuration from ${configFile.name}")
parseJsonc(configText)
}
"json" -> {
println("📄 Loading JSON configuration from ${configFile.name}")
Json.decodeFromString<Config>(configText)
}
else -> {
println("Error: Unsupported configuration file format: ${configFile.extension}")
return
}
}
} catch (e: Exception) {
println("Error reading configuration: ${e.message}")
return
}
println("\n✅ Configuration loaded successfully from config.json!")
println("\n✅ Configuration loaded successfully from ${configFile.name}!")
println("📍 Area: ${config.osmData.boundingBox.description}")
println("📂 Use local extract: ${config.osmData.useLocalExtract}")
println("📄 Local file path: ${config.osmData.localFilePath}")
@@ -67,6 +84,6 @@ fun main() {
println("5. Auto-open is disabled, file would remain closed")
}
println("\n✨ JSON configuration reading implementation complete!")
println("🎉 The app now successfully reads configuration from JSON instead of using hardcoded values.")
println("\n✨ JSON/JSONC configuration reading implementation complete!")
println("🎉 The app now successfully reads configuration from JSON and JSONC files with comment support!")
}