2016-05-07 2 views
5

Je voudrais lire la configuration suivante à partir d'un fichier HOCON (Typesafe Config) dans Kotlin.Lecture et traitement HOCON dans Kotlin

tablename: { 
    columns: [ 
    { item: { type: integer, key: true, null: false } } 
    { desc: { type: varchar, length: 64 } } 
    { quantity: { type: integer, null: false } } 
    { price: { type: decimal, precision: 14, scale: 3 } } 
    ] 
} 

En fait, je voudrais extraire la (les) colonne (s) clé (s). J'ai essayé ce qui suit jusqu'ici.

val metadata = ConfigFactory.parseFile(metafile) 
val keys = metadata.getObjectList("${tablename.toLowerCase()}.columns") 
        .filter { it.unwrapped().values.first().get("key") == true } 

Mais il échoue avec l'erreur suivante.

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
@kotlin.internal.InlineOnly public operator inline fun <@kotlin.internal.OnlyInputTypes K, V> kotlin.collections.Map<out kotlin.String, ???>.get(key: kotlin.String): ??? defined in kotlin.collections 

Il est clair que Kotlin n'est pas capable de comprendre le type de données du champ "valeur" dans la carte. Comment le déclarer ou laisser savoir à Kotlin?

De plus, il n'y a pas différents types et touches optionnelles dans cette carte. PS: Je sais qu'il existe quelques emballages pour Kotlin tels que Konfig et Klutter. J'espérais que si cela est facile à écrire, je pourrais éviter une autre bibliothèque.

MISE À JOUR 1:

J'ai essayé ce qui suit.

it.unwrapped().values.first().get<String, Boolean>("key") 

pour obtenir l'erreur de compilation suivante.

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
@kotlin.internal.InlineOnly public operator inline fun <@kotlin.internal.OnlyInputTypes K, V> kotlin.collections.Map<out kotlin.String, kotlin.Boolean>.get(key: kotlin.String): kotlin.Boolean? defined in kotlin.collections 

Et ce

it.unwrapped().values.first().get<String, Boolean?>("key") 

avec sortie

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
@kotlin.internal.InlineOnly public operator inline fun <@kotlin.internal.OnlyInputTypes K, V> kotlin.collections.Map<out kotlin.String, kotlin.Boolean?>.get(key: kotlin.String): kotlin.Boolean? defined in kotlin.collections 

MISE À JOUR 2:

En regardant la façon dont il est traité ailleurs, je suppose que je dois probablement utiliser réflexion. Essayer avec mon exposition limitée. Pas de chance jusqu'à présent.

+0

Je n'ai probablement pas besoin de déballer l'objet de configuration. Mais le traiter comme il n'a pas donné de résultats et c'était le plus proche que je pouvais l'amener à "imprimer" quelque chose. –

Répondre

7

Tenez compte de votre code, déconstruit ci-dessous:

val keys = metadata.getObjectList("tablename.columns") 
     .filter { 
      val item:ConfigObject = it 
      val unwrapped:Map<String,Any?> = item.unwrapped() 
      val values:Collection<Any?> = unwrapped.values 
      val firstValue:Any? = values.first() 
      firstValue.get("key") == true // does not compile 
     } 

De ce qui précède le problème devrait être évident. Vous devez aider le compilateur avec les informations que firstValue détient un Map comme ceci:

val firstValueMap = firstValue as Map<String,Any?> 
firstValueMap["key"] == true 
+0

Merci! Ça a marché. –

+0

J'ai dû aussi supprimer un avertissement UNCHECKED_CAST. –

2

Même si vous n'utilisez pas Klutter, je créé une mise à jour pour faire agir ConfigObject et Config uniformément la même. De la version Klutter 1.17.1 (en poussant à Maven central aujourd'hui) vous pouvez faire ce qui est représenté dans le test unitaire suivant en fonction de votre question.

La fonction recherche les colonnes clés:

fun findKeyColumns(cfg: Config, tableName: String): Map<String, ConfigObject> { 
    return cfg.nested(tableName).value("columns").asObjectList() 
      .map { it.keys.single() to it.value(it.keys.single()).asObject() } 
      .filter { 
       it.second.value("key").asBoolean(false) 
      } 
      .toMap() 
} 

est ici complète test unitaire pour cela:

// from http://stackoverflow.com/questions/37092808/reading-and-processing-hocon-in-kotlin 
@Test fun testFromSo37092808() { 
    // === mocked configuration file 

    val cfg = loadConfig(StringAsConfig(""" 
      products: { 
       columns: [ 
       { item: { type: integer, key: true, null: false } } 
       { desc: { type: varchar, length: 64 } } 
       { quantity: { type: integer, null: false } } 
       { price: { type: decimal, precision: 14, scale: 3 } } 
       ] 
      } 
      """)) 

    // === function to find which columns are key columns 

    fun findKeyColumns(cfg: Config, tableName: String): Map<String, ConfigObject> { 
     return cfg.nested(tableName).value("columns").asObjectList() 
       .map { it.keys.single() to it.value(it.keys.single()).asObject() } 
       .filter { 
        it.second.value("key").asBoolean(false) 
       } 
       .toMap() 
    } 

    // === sample usage 

    val productKeys = findKeyColumns(cfg, "products") 

    // we only have 1 in the test data, so grab the name and the values 
    val onlyColumnName = productKeys.entries.first().key 
    val onlyColumnObj = productKeys.entries.first().value 

    assertEquals ("item", onlyColumnName) 
    assertEquals (true, onlyColumnObj.value("key").asBoolean()) 
    assertEquals ("integer", onlyColumnObj.value("type").asString()) 
    assertEquals (false, onlyColumnObj.value("null").asBoolean()) 
} 

Vous pouvez retourner un comme ci-dessus Map, ou une liste de Pair pour la Nom de la colonne pour le mappage des paramètres, car le nom de la colonne ne se trouve pas dans ses paramètres.

La conception du fichier de configuration pourrait également être modifiée pour rendre le traitement de la configuration plus simple (par exemple, le nom de la table dans son objet de configuration, plutôt que comme une clé de gauche. dans l'objet et non comme une clé de gauche.)