Coverage Summary for Class: StandardSerializeEmitter (com.ghost.serialization.compiler)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| StandardSerializeEmitter |
100%
(1/1)
|
66.7%
(2/3)
|
50%
(12/24)
|
79.5%
(31/39)
|
76.6%
(154/201)
|
package com.ghost.serialization.compiler
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.ghost.serialization.compiler.GhostEmitterConstants as C
/**
* Standard serializer code generator for standard-sized DTOs (typically < 40 properties).
* Handles structured opening and closing of nested JSON brackets for flattened or wrapped properties.
*/
internal class StandardSerializeEmitter(
properties: List<GhostPropertyModel>,
originalClassName: ClassName,
writerClass: ClassName
) : BaseSerializeEmitter(properties, originalClassName, writerClass) {
/**
* Emits the standard object serialization instructions.
*
* @param code The target [CodeBlock.Builder].
* @param discriminator Optional polymorphic sealed class discriminator value.
* @param sealedDiscriminatorKey Discriminator key name.
*/
fun emit(
code: CodeBlock.Builder,
discriminator: String?,
sealedDiscriminatorKey: String
) {
code.addStatement(C.STR_WRITER_BEGIN_OBJ)
val hasDiscriminatorProperty = properties.any { it.jsonName == sealedDiscriminatorKey }
if (discriminator != null && !hasDiscriminatorProperty) {
code.addStatement(
C.STR_WRITER_NAME_TYPE_VAL,
sealedDiscriminatorKey,
discriminator
)
}
val currentPath = mutableListOf<String>()
properties.forEach { prop ->
val targetPath = prop.flattenPath?.dropLast(1)
?: prop.wrapPath
?: emptyList()
// Close objects that are not in the new target path
while (currentPath.isNotEmpty() && !isPrefix(currentPath, targetPath)) {
code.addStatement(C.STR_WRITER_END_OBJ)
currentPath.removeAt(currentPath.size - 1)
}
val isStringWriter = writerClass.simpleName == C.STR_GHOST_JSON_STRING_WRITER
val prefix = if (isStringWriter) {
C.STR_HS_PREFIX
} else {
C.STR_H_VAL_PREFIX
}
// Open new objects in the target path
targetPath.drop(currentPath.size).forEach { segment ->
code.addStatement(
C.STR_WRITER_WRITE_NAME_VAL,
prefix + segment.uppercase()
)
code.addStatement(C.STR_WRITER_BEGIN_OBJ)
currentPath.add(segment)
}
emitProperty(code, prop)
}
// Close remaining open objects
while (currentPath.isNotEmpty()) {
code.addStatement(C.STR_WRITER_END_OBJ)
currentPath.removeAt(currentPath.size - 1)
}
code.addStatement(C.STR_WRITER_END_OBJ)
}
/**
* Checks if a path prefix is a subset of the full path.
*/
private fun isPrefix(prefix: List<String>, full: List<String>): Boolean {
if (prefix.size > full.size) {
return false
}
for (i in prefix.indices) {
if (prefix[i] != full[i]) {
return false
}
}
return true
}
}