Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for JSON serialization via Jackson #52

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ See the [project website](https://saveourtool.github.io/kompiledb/) for document

- Compatible with Java 8.
- Can read and write `compile_commands.json`.
- No dependencies (except for [google/gson](https://github.com/google/gson)).
- No dependencies (except for [google/gson](https://github.com/google/gson) or
[FasterXML/jackson](https://github.com/FasterXML/jackson)).
The core library is JSON engine agnostic, so it's easy to add support for a
different JSON back-end, such as [FasterXML/jackson](https://github.com/FasterXML/jackson)
or [Kotlin/kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization).
different JSON back-end, such as
[Kotlin/kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization).
- Support for pluggable path translation between the local and the build
environments (such as [_Cygwin_](https://www.cygwin.com) or
[_WSL_](https://github.com/Microsoft/WSL)).
Expand Down Expand Up @@ -82,7 +83,7 @@ Then add the dependency as usual:

```kotlin
dependencies {
implementation("com.saveourtool.kompiledb:kompiledb-gson:1.0.0")
implementation("com.saveourtool.kompiledb:kompiledb-gson:1.0.1")
}
```

Expand Down
5 changes: 5 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ dependencies {
implementation(libs.nexus.publish.gradle.plugin)
implementation(libs.reckon.gradle.plugin)

constraints {
implementation(libs.jackson.databind)
implementation(libs.jackson.module.kotlin)
}

/*
* Workaround for https://github.com/gradle/gradle/issues/15383:
* Make version catalogs accessible from precompiled script plugins.
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[versions]
gson = "2.10.1"
jackson = "2.15.2"
kotlin = "1.9.0"
kotest = "5.6.2"
dokka = "1.8.20"
Expand All @@ -11,6 +12,8 @@ reckon-gradle-plugin = "0.13.2"

[libraries]
google-gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson"}
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson"}
gradle-kotlin-dsl-plugins = { module = "org.gradle.kotlin:gradle-kotlin-dsl-plugins", version = "4.0.15" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotest-assertions-json = { module = "io.kotest:kotest-assertions-json", version.ref = "kotest" }
Expand Down
5 changes: 5 additions & 0 deletions kompiledb-jackson/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.classpath
/.project
/.settings/
/build/
/target/
24 changes: 24 additions & 0 deletions kompiledb-jackson/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id("com.saveourtool.kompiledb.maven-repo-configuration")
id("com.saveourtool.kompiledb.kotlin-configuration")
id("com.saveourtool.kompiledb.testing-configuration")
id("com.saveourtool.kompiledb.publishing-configuration")
}

dependencies {
api(project(":kompiledb-core"))
implementation(libs.jackson.module.kotlin)

constraints {
implementation(libs.jackson.databind)
}

testImplementation(testFixtures(project(":kompiledb-core")))
}

tasks.withType<Test> {
filter {
includeTestsMatching("com.saveourtool.kompiledb.jackson.*")
isFailOnNoMatchingTests = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@file:JvmName("JsonIoExtensionsJackson")

package com.saveourtool.kompiledb.core

import com.saveourtool.kompiledb.core.JsonIo.Factory
import com.saveourtool.kompiledb.jackson.JacksonIo
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.json.JsonMapper.Builder

/**
* Creates a [JsonIo] instance which uses _Jackson_ to read and write JSON.
*/
val Factory.jackson: JsonIo
get() =
jackson()

/**
* Creates a [JsonIo] instance which uses _Jackson_ to read and write JSON.
*
* @param initBuilder the optional configuration for [Builder].
* @param initMapper the optional configuration for [JsonMapper].
*/
fun Factory.jackson(
initBuilder: Builder.() -> Unit = {},
initMapper: JsonMapper.() -> Unit = {},
): JsonIo =
JacksonIo(initBuilder, initMapper)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.saveourtool.kompiledb.jackson

import com.saveourtool.kompiledb.core.CompilationCommand
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode
import kotlin.collections.Map.Entry

internal object CompilationCommandDeserializer : JsonDeserializer<CompilationCommand>() {
private const val DIRECTORY = "directory"

private const val FILE = "file"

private const val ARGUMENTS = "arguments"

private const val COMMAND = "command"

private const val OUTPUT = "output"

private val FIELDS: Array<out String> = arrayOf(
DIRECTORY,
FILE,
ARGUMENTS,
COMMAND,
OUTPUT,
)

override fun deserialize(
p: JsonParser,
ctxt: DeserializationContext,
): CompilationCommand {
val json = p.readValueAsTree<ObjectNode>()

val props: Set<Entry<String, JsonNode>> = json.properties()

val unexpectedFields = props.asSequence().map(Entry<String, *>::key).toSet() - FIELDS
return when {
unexpectedFields.isEmpty() -> {
val directory: TextNode = json[DIRECTORY] as TextNode? ?: throwMismatchedInput(p, DIRECTORY)
val file: TextNode = json[FILE] as TextNode? ?: throwMismatchedInput(p, FILE)
val arguments: ArrayNode? = json[ARGUMENTS] as ArrayNode?
val command: TextNode? = json[COMMAND] as TextNode?
val output: TextNode? = json[OUTPUT] as TextNode?
TODO("Not implemented")
}

else -> throwMismatchedInput(
p,
"${unexpectedFields.size} unexpected field(s) encountered (${unexpectedFields.joinToString()})",
)
}
}

private fun throwMismatchedInput(p: JsonParser, message: String): Nothing =
throw MismatchedInputException.from(
p,
CompilationCommand::class.java,
message,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.saveourtool.kompiledb.jackson

import com.saveourtool.kompiledb.core.CompilationDatabase
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer

internal object CompilationDatabaseDeserializer : JsonDeserializer<CompilationDatabase>() {
override fun deserialize(
p: JsonParser,
ctxt: DeserializationContext,
): CompilationDatabase {
TODO("")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.saveourtool.kompiledb.jackson

import com.saveourtool.kompiledb.core.EnvPath
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.node.TextNode

internal object EnvPathDeserializer : JsonDeserializer<EnvPath>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): EnvPath =
p.readValueAsTree<TextNode>().textValue().let(::EnvPath)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.saveourtool.kompiledb.jackson

import com.saveourtool.kompiledb.core.CompilationCommand
import com.saveourtool.kompiledb.core.CompilationDatabase
import com.saveourtool.kompiledb.core.CompilationDatabase.Companion.COMPILE_COMMANDS_JSON
import com.saveourtool.kompiledb.core.EnvPath
import com.saveourtool.kompiledb.core.JsonIo
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import com.fasterxml.jackson.core.JacksonException
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.json.JsonMapper.Builder
import com.fasterxml.jackson.module.kotlin.KotlinFeature.StrictNullChecks
import com.fasterxml.jackson.module.kotlin.addDeserializer
import com.fasterxml.jackson.module.kotlin.jsonMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import java.io.IOException
import java.io.Reader
import java.nio.charset.Charset
import java.nio.file.Path
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
import kotlin.io.path.bufferedReader
import kotlin.io.path.div
import kotlin.io.path.isDirectory
import kotlin.io.path.name

internal class JacksonIo(
initBuilder: Builder.() -> Unit,
initMapper: JsonMapper.() -> Unit,
) : JsonIo {
private val mapper = jsonMapper {
initBuilder()

kotlinModule {
enable(StrictNullChecks)
}
.addDeserializer(EnvPath::class, EnvPathDeserializer)
.addDeserializer(CompilationCommand::class, CompilationCommandDeserializer)
.addDeserializer(CompilationDatabase::class, CompilationDatabaseDeserializer)
.let(this::addModule)
}
.apply(initMapper)
.setSerializationInclusion(NON_NULL)

override fun CompilationDatabase.toJson(): String =
mapper.writeValueAsString(this)

override fun CompilationCommand.toJson(): String =
mapper.writeValueAsString(this)

override fun String.toCompilationCommand(): Result<CompilationCommand> =
runCatching {
mapper.readValue(this)
}

override fun String.toCompilationDatabase(): Result<CompilationDatabase> =
runCatching {
mapper.readValue(this)
}

override fun Reader.readCompilationDatabase(): Result<CompilationDatabase> =
try {
success(mapper.readValue(this))
} catch (je: JacksonException) {
when (val cause = je.cause) {
is IOException -> throw cause
else -> failure(je)
}
} catch (ioe: IOException) {
throw ioe
}

override fun Path.readCompilationDatabase(charset: Charset): Result<CompilationDatabase> =
when {
isDirectory() && name != COMPILE_COMMANDS_JSON -> (this / COMPILE_COMMANDS_JSON).readCompilationDatabase(charset)

else -> bufferedReader(charset).use { reader ->
reader.readCompilationDatabase()
}
}
}
Loading
Loading