Repository: cjbooms/fabrikt
Pull request #76 (2021-08-17 22:23:34)

Serialize additional properties as top level json properties
Adding in Jackson annotations and getter to allow for additional properties to be serialised back to json as top level properties. Previously deserialisation worked, but on re-serialisation they were being placed inside a map called properties.


Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
index 73757df..83f02c8 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
@@ -62,10 +62,18 @@ object PropertyUtils {
 
         if (this is PropertyInfo.AdditionalProperties) {
             property.initializer("mutableMapOf()")
+            property.addAnnotation(JacksonMetadata.ignore)
             val value =
                 if (typeInfo is KotlinTypeInfo.MapTypeAdditionalProperties)
                     Map::class.asTypeName().parameterizedBy(String::class.asTypeName(), parameterizedType)
                 else parameterizedType
+            classBuilder.addFunction(
+                FunSpec.builder("get")
+                    .returns(Map::class.asTypeName().parameterizedBy(String::class.asTypeName(), value))
+                    .addStatement("return $name")
+                    .addAnnotation(JacksonMetadata.anyGetter)
+                    .build()
+            )
             classBuilder.addFunction(
                 FunSpec.builder("set")
                     .addParameter("name", String::class)
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonMetadata.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonMetadata.kt
index ed6c615..4b4cae3 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonMetadata.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonMetadata.kt
@@ -12,6 +12,8 @@ object JacksonMetadata {
     private val JSON_TYPE_INFO_CLASS = ClassName("com.fasterxml.jackson.annotation", "JsonTypeInfo")
     private val JSON_SUB_TYPES_CLASS = ClassName("com.fasterxml.jackson.annotation", "JsonSubTypes")
     private val JSON_ANY_SETTER = ClassName("com.fasterxml.jackson.annotation", "JsonAnySetter")
+    private val JSON_ANY_GETTER = ClassName("com.fasterxml.jackson.annotation", "JsonAnyGetter")
+    private val JSON_IGNORE = ClassName("com.fasterxml.jackson.annotation", "JsonIgnore")
 
     val JSON_VALUE = AnnotationSpec.builder(JSON_VALUE_CLASS).build()
 
@@ -26,6 +28,10 @@ object JacksonMetadata {
         .addMember("%S", name).build()
 
     val anySetter = AnnotationSpec.builder(JSON_ANY_SETTER).build()
+    val anyGetter = AnnotationSpec.builder(JSON_ANY_GETTER).build()
+    val ignore = AnnotationSpec.builder(JSON_IGNORE)
+        .useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
+        .build()
 
     fun basePolymorphicType(discriminatorFieldName: String) = AnnotationSpec
         .builder(JSON_TYPE_INFO_CLASS)


Test patch:
diff --git a/src/test/resources/examples/externalReferences/models/Models.kt b/src/test/resources/examples/externalReferences/models/Models.kt
index 47624dd..a1d1e64 100644
--- a/src/test/resources/examples/externalReferences/models/Models.kt
+++ b/src/test/resources/examples/externalReferences/models/Models.kt
@@ -1,6 +1,8 @@
 package examples.externalReferences.models
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter
 import com.fasterxml.jackson.annotation.JsonAnySetter
+import com.fasterxml.jackson.annotation.JsonIgnore
 import com.fasterxml.jackson.annotation.JsonProperty
 import com.fasterxml.jackson.annotation.JsonSubTypes
 import com.fasterxml.jackson.annotation.JsonTypeInfo
@@ -47,8 +49,12 @@ data class ExternalObjectTwo(
     @get:Valid
     val listOthers: List<ExternalObjectThree>? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Map<String, ExternalObjectFour>> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Map<String, ExternalObjectFour>> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Map<String, ExternalObjectFour>) {
         properties[name] = value
diff --git a/src/test/resources/examples/githubApi/models/Models.kt b/src/test/resources/examples/githubApi/models/Models.kt
index e386fbe..a5bf47a 100644
--- a/src/test/resources/examples/githubApi/models/Models.kt
+++ b/src/test/resources/examples/githubApi/models/Models.kt
@@ -1,6 +1,8 @@
 package examples.githubApi.models
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter
 import com.fasterxml.jackson.annotation.JsonAnySetter
+import com.fasterxml.jackson.annotation.JsonIgnore
 import com.fasterxml.jackson.annotation.JsonProperty
 import com.fasterxml.jackson.annotation.JsonValue
 import java.time.OffsetDateTime
@@ -20,8 +22,12 @@ data class BulkEntityDetails(
     @get:Valid
     val entities: List<EntityDetails>
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Any> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Any> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Any) {
         properties[name] = value
@@ -34,8 +40,12 @@ data class EntityDetails(
     @get:NotNull
     val id: String
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Any> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Any> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Any) {
         properties[name] = value
@@ -50,8 +60,12 @@ data class EventResults(
     @get:Valid
     val changeEvents: List<Event>
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Any> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Any> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Any) {
         properties[name] = value
@@ -68,8 +82,12 @@ data class Event(
     @get:NotNull
     val data: Map<String, Any>
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Any> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Any> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Any) {
         properties[name] = value
diff --git a/src/test/resources/examples/mapExamples/models/Models.kt b/src/test/resources/examples/mapExamples/models/Models.kt
index 29d4055..92f678f 100644
--- a/src/test/resources/examples/mapExamples/models/Models.kt
+++ b/src/test/resources/examples/mapExamples/models/Models.kt
@@ -1,6 +1,8 @@
 package examples.mapExamples.models
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter
 import com.fasterxml.jackson.annotation.JsonAnySetter
+import com.fasterxml.jackson.annotation.JsonIgnore
 import com.fasterxml.jackson.annotation.JsonProperty
 import javax.validation.Valid
 import kotlin.Any
@@ -54,8 +56,12 @@ data class MapHolder(
     @get:Valid
     val inlinedComplexObjectWithTypedMap: MapHolderInlinedComplexObjectWithTypedMap? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Map<String, ExternalObjectFour>> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Map<String, ExternalObjectFour>> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Map<String, ExternalObjectFour>) {
         properties[name] = value
@@ -88,8 +94,12 @@ data class ComplexObjectWithUntypedMap(
     @get:JsonProperty("code")
     val code: Int? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Any> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Any> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Any) {
         properties[name] = value
@@ -113,8 +123,12 @@ data class ComplexObjectWithTypedMap(
     @get:JsonProperty("code")
     val code: Int? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, ComplexObjectWithTypedMapValue> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, ComplexObjectWithTypedMapValue> = properties
+
     @JsonAnySetter
     fun set(name: String, value: ComplexObjectWithTypedMapValue) {
         properties[name] = value
@@ -129,8 +143,12 @@ data class MapHolderInlinedComplexObjectWithUntypedMap(
     @get:JsonProperty("code")
     val code: Int? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, Any> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, Any> = properties
+
     @JsonAnySetter
     fun set(name: String, value: Any) {
         properties[name] = value
@@ -154,8 +172,12 @@ data class MapHolderInlinedComplexObjectWithTypedMap(
     @get:JsonProperty("code")
     val code: Int? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, InlinedComplexObjectWithTypedMapValue> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, InlinedComplexObjectWithTypedMapValue> = properties
+
     @JsonAnySetter
     fun set(name: String, value: InlinedComplexObjectWithTypedMapValue) {
         properties[name] = value
@@ -176,8 +198,12 @@ data class ComplexObjectWithRefTypedMap(
     @get:JsonProperty("code")
     val code: Int? = null
 ) {
+    @get:JsonIgnore
     val properties: MutableMap<String, SomeRef> = mutableMapOf()
 
+    @JsonAnyGetter
+    fun get(): Map<String, SomeRef> = properties
+
     @JsonAnySetter
     fun set(name: String, value: SomeRef) {
         properties[name] = value