2016-05-07 11 views
5

Vorrei leggere la seguente configurazione da un file HOCON (Typesafe Config) in Kotlin.Lettura ed elaborazione di HOCON in 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 } } 
    ] 
} 

In effetti vorrei estrarre la colonna chiave (s). Ho provato quanto segue finora.

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

Ma non riesce con il seguente errore.

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 

È chiaro che Kotlin non è in grado di comprendere il tipo di dati del campo "valore" nella Mappa. Come posso dichiararlo o far sapere a Kotlin?

Inoltre, non ci sono diversi tipi e chiavi opzionali in questa mappa.

PS: So che ci sono un paio di wrapper disponibili per Kotlin come Konfig e Klutter. Speravo che se fosse facile scrivere potrei evitare un'altra libreria.

UPDATE 1:

Ho provato quanto segue.

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

per ottenere il seguente errore del compilatore.

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 

E questo

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

con uscita

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 

UPDATE 2:

Guardando il modo in cui viene gestita altrove, immagino probabilmente ho bisogno di usare riflessione. Provando con la mia esposizione limitata. Nessuna fortuna finora.

+0

Probabilmente non ho bisogno di scartare l'oggetto config. Ma occuparsene non ha dato alcun risultato e questo è stato il più vicino possibile per "stampare" qualcosa. –

risposta

7

consideri il codice, decostruito di seguito:

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 
     } 

Da quanto sopra il problema dovrebbe essere ovvio. È necessario aiutare il compilatore con le informazioni che firstValue detiene una Map in questo modo:

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

Grazie! Ha funzionato. –

+0

Ho dovuto sopprimere anche un avvertimento UNCHECKED_CAST. –

2

Anche se non si utilizza Klutter, ho creato un aggiornamento per per renderlo ConfigObject e Config agire in modo uniforme lo stesso. Dalla versione di Klutter 1.17.1 in poi (premendo su Maven central oggi) puoi fare ciò che è rappresentato nel seguente test di unità in base alla tua domanda.

La funzione di ricerca delle colonne chiave:

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() 
} 

Ecco test completo unità per questo:

// 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()) 
} 

Si potrebbe restituire un Map come sopra, o un elenco di Pair per la nome della colonna alla mappatura delle impostazioni poiché il nome della colonna non è all'interno delle sue impostazioni.

La progettazione del file di configurazione potrebbe anche essere modificata per rendere più semplice l'elaborazione della configurazione (ad esempio il nome della tabella all'interno del suo oggetto di configurazione, piuttosto che come tasto di sinistra. nell'oggetto e non come tasto sul lato sinistro.)