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 package org.example
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable @Serializable
data class Config( data class Config(
@@ -41,4 +42,87 @@ data class OriginConfig(
data class OutputConfig( data class OutputConfig(
val fileName: String, val fileName: String,
val autoOpen: Boolean 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,9 +4,10 @@ import kotlinx.serialization.json.Json
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import java.io.File
class ConfigTest { class ConfigTest {
@Test @Test
fun testConfigDeserialization() { fun testConfigDeserialization() {
val jsonString = """ val jsonString = """
@@ -35,9 +36,9 @@ class ConfigTest {
} }
} }
""".trimIndent() """.trimIndent()
val config = Json.decodeFromString<Config>(jsonString) val config = Json.decodeFromString<Config>(jsonString)
assertNotNull(config) assertNotNull(config)
assertEquals(false, config.osmData.useLocalExtract) assertEquals(false, config.osmData.useLocalExtract)
assertEquals("virginia.osm.pbf", config.osmData.localFilePath) assertEquals("virginia.osm.pbf", config.osmData.localFilePath)
@@ -52,4 +53,155 @@ class ConfigTest {
assertEquals("municipality.glb", config.output.fileName) assertEquals("municipality.glb", config.output.fileName)
assertEquals(true, config.output.autoOpen) 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 LOAD AND DEMONSTRATE JSON CONFIGURATION
---------------------------------------------------------------- */ ---------------------------------------------------------------- */
val configFile = File("config.json") // Try to find configuration file (prefer .jsonc, fallback to .json)
if (!configFile.exists()) { val configFile = when {
println("Error: config.json not found in current directory") File("config.jsonc").exists() -> File("config.jsonc")
println("Please ensure config.json exists in the working directory") File("config.json").exists() -> File("config.json")
return 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 config = try {
val configText = configFile.readText() 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) { } catch (e: Exception) {
println("Error reading configuration: ${e.message}") println("Error reading configuration: ${e.message}")
return return
} }
println("\n✅ Configuration loaded successfully from config.json!") println("\n✅ Configuration loaded successfully from ${configFile.name}!")
println("📍 Area: ${config.osmData.boundingBox.description}") println("📍 Area: ${config.osmData.boundingBox.description}")
println("📂 Use local extract: ${config.osmData.useLocalExtract}") println("📂 Use local extract: ${config.osmData.useLocalExtract}")
println("📄 Local file path: ${config.osmData.localFilePath}") println("📄 Local file path: ${config.osmData.localFilePath}")
@@ -67,6 +84,6 @@ fun main() {
println("5. Auto-open is disabled, file would remain closed") println("5. Auto-open is disabled, file would remain closed")
} }
println("\n✨ JSON configuration reading implementation complete!") println("\n✨ JSON/JSONC configuration reading implementation complete!")
println("🎉 The app now successfully reads configuration from JSON instead of using hardcoded values.") println("🎉 The app now successfully reads configuration from JSON and JSONC files with comment support!")
} }