From 61186961cd39dbbd59649fa4fa7a6c9d2baf1615 Mon Sep 17 00:00:00 2001 From: Minsoo Cheong <54794500+mscheong01@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:14:21 +0900 Subject: [PATCH] generate json converter functions for krotoDC dataclasses (#25) * generate json converter functions for krotoDC dataclasses * add `protobuf-java-util` dependency to README --- README.md | 1 + generator/build.gradle.kts | 1 + .../specgenerators/file/ConverterGenerator.kt | 13 +++++ .../specgenerators/file/DataClassGenerator.kt | 2 + .../function/FromJsonFunctionGenerator.kt | 53 +++++++++++++++++++ .../function/ToJsonFunctionGenerator.kt | 37 +++++++++++++ .../mscheong01/krotodc/JsonConverterTest.kt | 39 ++++++++++++++ 7 files changed, 146 insertions(+) create mode 100644 generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/FromJsonFunctionGenerator.kt create mode 100644 generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/ToJsonFunctionGenerator.kt create mode 100644 generator/src/test/kotlin/io/github/mscheong01/krotodc/JsonConverterTest.kt diff --git a/README.md b/README.md index b275213..8be0339 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ In your project's `build.gradle.kts` file, add the following dependencies: ```kotlin dependencies { implementation("com.google.protobuf:protobuf-java:3.25.3") + implementation("com.google.protobuf:protobuf-java-util:3.25.3") implementation("io.grpc:grpc-stub:1.61.1") implementation("io.grpc:grpc-kotlin-stub:1.4.1") implementation("io.github.mscheong01:krotoDC-core:1.0.7") diff --git a/generator/build.gradle.kts b/generator/build.gradle.kts index 074a1f8..1f0da9b 100644 --- a/generator/build.gradle.kts +++ b/generator/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { // implementation("io.grpc:grpc-protobuf:${rootProject.ext["grpcJavaVersion"]}") // https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java implementation("com.google.protobuf:protobuf-java:${rootProject.ext["protobufVersion"]}") + implementation("com.google.protobuf:protobuf-java-util:${rootProject.ext["protobufVersion"]}") implementation(kotlin("reflect")) implementation("com.squareup:kotlinpoet:${rootProject.ext["kotlinPoetVersion"]}") diff --git a/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/ConverterGenerator.kt b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/ConverterGenerator.kt index b78530f..806e853 100644 --- a/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/ConverterGenerator.kt +++ b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/ConverterGenerator.kt @@ -19,8 +19,10 @@ import com.squareup.kotlinpoet.FunSpec import io.github.mscheong01.krotodc.import.FunSpecsWithImports import io.github.mscheong01.krotodc.import.Import import io.github.mscheong01.krotodc.specgenerators.FileSpecGenerator +import io.github.mscheong01.krotodc.specgenerators.function.FromJsonFunctionGenerator import io.github.mscheong01.krotodc.specgenerators.function.MessageToDataClassFunctionGenerator import io.github.mscheong01.krotodc.specgenerators.function.MessageToProtoFunctionGenerator +import io.github.mscheong01.krotodc.specgenerators.function.ToJsonFunctionGenerator import io.github.mscheong01.krotodc.util.addAllImports import io.github.mscheong01.krotodc.util.isPredefinedType import io.github.mscheong01.krotodc.util.krotoDCPackage @@ -29,6 +31,8 @@ class ConverterGenerator : FileSpecGenerator { val messageToDataClassGenerator = MessageToDataClassFunctionGenerator() val messageToProtoGenerator = MessageToProtoFunctionGenerator() + val toJsonFunctionGenerator = ToJsonFunctionGenerator() + val fromJsonFunctionGenerator = FromJsonFunctionGenerator() override fun generate(fileDescriptor: Descriptors.FileDescriptor): List { val fileSpecs = mutableMapOf() @@ -64,6 +68,15 @@ class ConverterGenerator : FileSpecGenerator { funSpecs.addAll(it.funSpecs) imports.addAll(it.imports) } + toJsonFunctionGenerator.generate(messageDescriptor).let { + funSpecs.addAll(it.funSpecs) + imports.addAll(it.imports) + } + fromJsonFunctionGenerator.generate(messageDescriptor).let { + funSpecs.addAll(it.funSpecs) + imports.addAll(it.imports) + } + messageDescriptor.nestedTypes.forEach { nestedType -> generateConvertersForMessageDescriptor(nestedType).let { funSpecs.addAll(it.funSpecs) diff --git a/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/DataClassGenerator.kt b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/DataClassGenerator.kt index 0efa800..c72054d 100644 --- a/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/DataClassGenerator.kt +++ b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/file/DataClassGenerator.kt @@ -205,6 +205,8 @@ class DataClassGenerator : FileSpecGenerator { .addMember("forProto = %L::class", messageDescriptor.protobufJavaTypeName) .build() ) + + dataClassBuilder.addType(TypeSpec.companionObjectBuilder().build()) return TypeSpecsWithImports( listOf(dataClassBuilder.build()), imports diff --git a/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/FromJsonFunctionGenerator.kt b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/FromJsonFunctionGenerator.kt new file mode 100644 index 0000000..430b512 --- /dev/null +++ b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/FromJsonFunctionGenerator.kt @@ -0,0 +1,53 @@ +// Copyright 2024 Minsoo Cheong +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package io.github.mscheong01.krotodc.specgenerators.function + +import com.google.protobuf.Descriptors.Descriptor +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.ParameterSpec +import io.github.mscheong01.krotodc.import.FunSpecsWithImports +import io.github.mscheong01.krotodc.import.Import +import io.github.mscheong01.krotodc.specgenerators.FunSpecGenerator +import io.github.mscheong01.krotodc.util.krotoDCTypeName +import io.github.mscheong01.krotodc.util.protobufJavaTypeName + +class FromJsonFunctionGenerator : FunSpecGenerator { + override fun generate(descriptor: Descriptor): FunSpecsWithImports { + val imports = mutableSetOf() + val generatedType = descriptor.krotoDCTypeName + val generatedTypeCompanion = ClassName( + generatedType.packageName, + *generatedType.simpleNames.toTypedArray(), + "Companion" + ) + val protoType = descriptor.protobufJavaTypeName + val functionBuilder = FunSpec.builder("fromJson") + .addParameter( + ParameterSpec.builder( + "json", + String::class + ).build() + ) + .receiver(generatedTypeCompanion) + .returns(generatedType) + functionBuilder.addCode("return %L.newBuilder().apply { JsonFormat.parser().ignoringUnknownFields().merge(json, this@apply) }.build().toDataClass();\n", protoType) + + imports.add(Import("com.google.protobuf.util", listOf("JsonFormat"))) + return FunSpecsWithImports( + listOf(functionBuilder.build()), + imports + ) + } +} diff --git a/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/ToJsonFunctionGenerator.kt b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/ToJsonFunctionGenerator.kt new file mode 100644 index 0000000..ae4e23d --- /dev/null +++ b/generator/src/main/kotlin/io/github/mscheong01/krotodc/specgenerators/function/ToJsonFunctionGenerator.kt @@ -0,0 +1,37 @@ +// Copyright 2024 Minsoo Cheong +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package io.github.mscheong01.krotodc.specgenerators.function + +import com.google.protobuf.Descriptors.Descriptor +import com.squareup.kotlinpoet.FunSpec +import io.github.mscheong01.krotodc.import.FunSpecsWithImports +import io.github.mscheong01.krotodc.import.Import +import io.github.mscheong01.krotodc.specgenerators.FunSpecGenerator +import io.github.mscheong01.krotodc.util.krotoDCTypeName + +class ToJsonFunctionGenerator : FunSpecGenerator { + override fun generate(descriptor: Descriptor): FunSpecsWithImports { + val imports = mutableSetOf() + val generatedType = descriptor.krotoDCTypeName + val functionBuilder = FunSpec.builder("toJson") + .receiver(generatedType) + .returns(String::class) + functionBuilder.addCode("return JsonFormat.printer().print(this@toJson.toProto())") + imports.add(Import("com.google.protobuf.util", listOf("JsonFormat"))) + return FunSpecsWithImports( + listOf(functionBuilder.build()), + imports + ) + } +} diff --git a/generator/src/test/kotlin/io/github/mscheong01/krotodc/JsonConverterTest.kt b/generator/src/test/kotlin/io/github/mscheong01/krotodc/JsonConverterTest.kt new file mode 100644 index 0000000..faae7e2 --- /dev/null +++ b/generator/src/test/kotlin/io/github/mscheong01/krotodc/JsonConverterTest.kt @@ -0,0 +1,39 @@ +// Copyright 2024 Minsoo Cheong +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package io.github.mscheong01.krotodc + +import io.github.mscheong01.test.Person +import io.github.mscheong01.test.krotodc.person.fromJson +import io.github.mscheong01.test.krotodc.person.toDataClass +import io.github.mscheong01.test.krotodc.person.toJson +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class JsonConverterTest { + + @Test + fun `test simple message`() { + val proto = Person.newBuilder() + .setName("John") + .setAge(30) + .build() + val dataClass = proto.toDataClass() + val json = dataClass.toJson() + Assertions.assertThat(json).isEqualTo( + "{\n \"name\": \"John\",\n \"age\": 30\n}" + ) + val deserializedDataClass = io.github.mscheong01.test.krotodc.Person.fromJson(json) + Assertions.assertThat(dataClass).isEqualTo(deserializedDataClass) + } +}