域类和 YAML 文件之间具有不同名称的属性之间的映射

问题描述 投票:0回答:1

我正在创建一个 YAML 解析器,我想要实现的功能之一是域类的属性应该能够具有与 YAML 表示中使用的名称不同的名称。例如,YAML 中的属性可能具有出生城市的名称,而在 Kotlin 中,它的名称可能是来自。因此,为了解决具有不同名称的属性之间的映射,我实现了一个 YamlArg 注释,该注释可用于域类构造函数的参数,指示 YAML 中的相应名称(例如 @YamlArg("city ofbirth")) 然而,我一生都无法让注释具有任何价值。在 AbstractYamlParser 的第 95 行中,我在域类构造函数中执行搜索,以查找名称与 yaml 文件中的键匹配的属性。它能够找到与名称匹配的属性,但找不到我尝试调用注释类的属性。

 val paramType = type.memberProperties.find{ it.name == key || it.findAnnotation<YamlArg>()?.name == key }
因此,在调试时,我看到 memberProperties 具有 Student 类的所有属性,但显然我调用注释类的属性名称与输入 YAML 文件不匹配。 总之,我不确定如何准确地将 YAML 文件中的不同名称传递给类文件。我将保留 Student 类、YamlArg 注释类和我尝试在下面运行的测试。还将留下我的解析器文件,非常感谢任何帮助!

class Student @JvmOverloads constructor (
    val name: String,
    val nr: Int,
    @YamlArg("city of birth") val from: String,
    @YamlConvert(YamlToDate::class) val birth: LocalDate? = null,
    val address: Address? = null,
    val grades: List<Grade> = emptyList()
)

annotation class YamlArg(val name:String)
fun `Test YamlParserReflect with @YamlArg`() {
        val yaml = """
            name: John
            nr: 123
            city of birth: New York
        """.trimIndent()

        val student = YamlParserReflect.yamlParser(Student::class).parseObject(yaml.reader())

        assertEquals("John", student.name)
        assertEquals(123, student.nr)
        assertEquals("New York", student.from)
    }
import java.io.Reader
import kotlin.contracts.contract
import kotlin.reflect.KClass
import kotlin.reflect.full.*

abstract class AbstractYamlParser<T : Any>(val type: KClass<T>) : YamlParser<T> {
    /**
     * Used to get a parser for other Type using this same parsing approach.
     */
    abstract fun <T : Any> yamlParser(type: KClass<T>) : AbstractYamlParser<T>
    /**
     * Creates a new instance of T through the first constructor
     * that has all the mandatory parameters in the map and optional parameters for the rest.
     */
    abstract fun newInstance(args: Map<String, Any>): T

    final override fun parseObject(yaml: Reader): T {

        // Mapa para guardar os parâmetros do Objeto a criar
        val parameters: MutableMap<String, Any> = mutableMapOf()

        // Variáveis para controlar a identação do ficheiro YAML
        var count = 0
        var canCount = true

        // Ficheiro de texto
        val lines = yaml.readLines()
        var idx = 0

        while ( idx < lines.size ){

            val line = lines[idx]

            // Se a linha for branca, não há nada a analizar
            if(line.isBlank()) { idx++ }
            else {

                // Verificar o número de espaços devido à identação YAML
                if(canCount){
                    for(char in line) if(char == ' ') count++ else break
                    canCount = false
                }

                // Novo objeto
                if(line.trim().endsWith(":")){

                    // Lista para guardar os parâmetros do objeto
                    val newObject: MutableList<String> = mutableListOf()
                    // Nome do parâmetro
                    val key = line.trim().dropLast(1) // Elimina os ':'

                    // Verificamos se existe um parâmetro no construtor com o mesmo nome do parâmetro
                    val obj = type.memberProperties.find { it.name.lowercase() == key }
                        ?: throw IllegalArgumentException("Object '$key' was not found in class '${type.simpleName}'.")

                    // Verificamos a classe do parâmetro
                    // Caso este seja uma lista, pretendemos criar objetos com o tipo dos parâmetros da lista
                    val objClass =
                        if(obj.returnType.classifier == List::class){ obj.returnType.arguments.first().type!!.classifier as KClass<*> }
                        else{ obj.returnType.classifier as KClass<T> }

                    idx++

                    // Percorremos pelos parâmetros do objeto e adicionamo-los à lista 'newObject'
                    while( idx < lines.size ){

                        // Quando a linha já não tiver um elemento do objeto atual
                        if(!lines[idx].drop(count).startsWith(" ")) break

                        // Adicionar o parâmetro ao objeto atual
                        newObject.add(lines[idx])
                        idx++
                    }

                    // Criamos o novo objeto chamando a função com o tipo pretendido e os parâmetros recolhidos
                    parameters[key] = YamlParserReflect.yamlParser(objClass).parseObject(newObject.joinToString("\n").reader())
                }
                else if (line.trim().startsWith("-")){

                    // Caso se trate de uma lista, chamamos o 'parseList'
                    return parseList(lines.joinToString("\n").reader()) as T
                }
                else {

                    val words = line.split(": ")

                    // Dividimos a linha para obter strings com o parâmetro 'key' e o seu valor 'value'
                    val key = words[0].drop(count)
                    val strValue = words[1]

                    // Procuramos no construtor da classe por um parâmetro com o mesmo nome da 'key'
                    val paramType = type.memberProperties.find{ it.name == key || it.findAnnotation<YamlArg>()?.name == key }
                        ?: throw IllegalArgumentException("Parameter '$key' was not found in the class '${type.simpleName}'.")

                    // Dada a classe do parâmetro, alteramos a classe do 'value'
                    val value =
                        when(paramType.returnType.classifier as KClass<*>){
                            Int::class -> strValue.toInt()
                            Double::class -> strValue.toDouble()
                            Float::class -> strValue.toFloat()
                            Long::class -> strValue.toDouble()
                            Char::class -> strValue.toCharArray().first()
                            else -> strValue
                        }

                    // Adicionamos o novo parâmetro ao mapa 'parameters'
                    parameters[key] = value
                    idx++
                }
            }
        }

        return newInstance(parameters)
    }

    final override fun parseList(yaml: Reader): List<T> {

        val returnList = mutableListOf<T>()

        val lines = yaml.readLines()
        var idx = 0

        var count = 0
        var countSpaces = true

        while (idx < lines.size) {

            if(lines[idx].trim().endsWith("-")){

                if(countSpaces){
                    for (char in lines[idx]) if (char == ' ') count++ else break
                    countSpaces = false
                }

                val currentObject = mutableListOf<String>()

                if(lines[0].split(": ").size == 2) {

                    val objTypeName = returnList.first().toString().split(":")[0].trimStart()

                    // Os dois espaços são necessários, de outra forma seria necessário ajeitar a variável 'count'
                    currentObject.add("  $objTypeName: $objTypeName")
                }

                idx++

                while(idx < lines.size && lines[idx].isNotBlank() && !lines[idx].drop(count).startsWith("-")){

                    currentObject.add(lines[idx])
                    idx++
                }

                returnList.add(parseObject(currentObject.joinToString("\n").reader()))
            }
            else if(lines[idx].isNotBlank()){

                // Tipos Primitivos da forma '- Tipo Primitivo' devem ser tratados logo em 'parseList'
                val value = lines[idx].dropWhile { it == ' ' || it == '-' }

                val valueType =
                    when (type) {
                        Char::class -> value.toCharArray().first()
                        Int::class -> value.toInt()
                        Long::class -> value.toLong()
                        Double::class -> value.toDouble()
                        else -> value
                    } as T

                idx++
                returnList.add(valueType)

            }
            else { idx++ }

        }

        return returnList
    }

}
import java.security.spec.InvalidParameterSpecException
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaConstructor
import kotlin.reflect.jvm.reflect

/**
 * A YamlParser that uses reflection to parse objects.
 */
class YamlParserReflect<T : Any>(type: KClass<T>) : AbstractYamlParser<T>(type) {
    companion object {
        /**
         *Internal cache of YamlParserReflect instances.
         */
        private val yamlParsers: MutableMap<KClass<*>, YamlParserReflect<*>> = mutableMapOf()
        /**
         * Creates a YamlParser for the given type using reflection if it does not already exist.
         * Keep it in an internal cache of YamlParserReflect instances.
         */
        fun <T : Any> yamlParser(type: KClass<T>): AbstractYamlParser<T> {
            return yamlParsers.getOrPut(type) { YamlParserReflect(type) } as YamlParserReflect<T>
        }
    }
    /**
     * Used to get a parser for other Type using the same parsing approach.
     */
    override fun <T : Any> yamlParser(type: KClass<T>) = YamlParserReflect.yamlParser(type)
    /**
     * Creates a new instance of T through the first constructor
     * that has all the mandatory parameters in the map and optional parameters for the rest.
     */
    override fun newInstance(args: Map<String, Any>): T {
        val params: MutableMap<KParameter, Any?> = mutableMapOf()

        // Get the constructor of the class
        val constructor = type.primaryConstructor
            ?: throw IllegalArgumentException("Class ${type.simpleName} does not have a primary constructor.")

        for (parameter in constructor.parameters) {
            // Check if the parameter has a custom converter specified
            val convertAnnotation = parameter.findAnnotation<YamlConvert>()
            val argValue = args[parameter.name]

            val value = when {
                // Use custom converter if specified
                convertAnnotation != null && argValue is String -> {
                    val converterInstance = convertAnnotation.converter.createInstance()

                    // Ensure the converter has the required function
                    val convertFunction = converterInstance::class.functions.find {
                        it.name == "convertYamlToObject" && it.parameters.size == 2
                    } ?: throw InvalidParameterSpecException("Custom converter must have a function 'convertYamlToObject(yaml: String)'")

                    convertFunction.call(converterInstance, argValue)
                }
                else -> argValue
            }

            if (value == null && !parameter.isOptional) {
                throw IllegalArgumentException("The parameter ${parameter.name} was not assigned a value.")
            }

            // Special handling for 'grades' property
            if (parameter.name == "grades" && value == null) {
                params[parameter] = emptyList<T>() // or any default value you want
            } else {
                params[parameter] = value
            }
        }

        return constructor.callBy(params)
    }
}
kotlin debugging constructor yaml annotations
1个回答
0
投票

通过构造函数访问参数解决

val paramType = type.constructors.first().parameters.find{ it.name == key || it.findAnnotation<YamlArg>()?.name == key }

然后将 paramType.name 属性赋予一个新密钥

var keyA = paramType.name

最后通过它以防它存在

if(keyA != null){
   parameters[keyA] = value
   keyA == null
}
else parameters[key] = value
© www.soinside.com 2019 - 2024. All rights reserved.