2016-05-07 14 views
5

Chciałbym przeczytać poniższą konfigurację z pliku HOCON (Typesafe Config) do Kotlin.Czytanie i przetwarzanie HOCON w 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 } } 
    ] 
} 

W rzeczywistości chciałbym wyodrębnić kluczowe kolumny (y). Próbowałem do tej pory następujące.

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

Ale zawiedzie z następującym błędem.

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 

Jest oczywiste, że Kotlin nie jest w stanie zrozumieć typu danych pola "wartości" na mapie. Jak mogę to zgłosić lub powiadomić Kotlin?

Nie ma również różnych typów i opcjonalnych kluczy na tej mapie.

PS: Wiem, że dla Kotlina dostępnych jest kilka owijaczy, takich jak Konfig i Klutter. Miałem nadzieję, że jeśli to będzie łatwe do napisania, mogę uniknąć kolejnej biblioteki.

UPDATE 1:

Próbowałem następujące.

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

Aby uzyskać następujący błąd kompilatora.

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 

I to

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

z wyjściem

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:

Patrząc na sposób, w jaki jest obsługiwany gdzie indziej, myślę, że prawdopodobnie trzeba użyć odbicie. Próbuję tego z moją ograniczoną ekspozycją. Do tej pory nie było szczęścia.

+0

Prawdopodobnie nie trzeba odwijać obiektu konfiguracyjnego. Ale radzenie sobie z nim tak, jak jest, nie przyniosło żadnych rezultatów i było to najbliżej, jakie mogłem przynieść, aby "wydrukować" coś. –

Odpowiedz

7

Zastanów się kod, dekonstrukcji poniżej:

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 
     } 

Z powyższego problemu powinno być oczywiste. Trzeba pomóc kompilator z informacją, że firstValue trzyma Map tak:

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

Dzięki! Zadziałało. –

+0

Musiałem również wyłączyć ostrzeżenie UNCHECKED_CAST. –

2

Nawet jeśli nie używasz Klutter, stworzyłem aktualizacji na to, aby uczynić ConfigObject i Config akt równomiernie samo. Od wersji Klutter 1.17.1 (dalej do dzisiejszego centrum Maven) możesz zrobić to, co jest reprezentowane w poniższym teście jednostki na podstawie twojego pytania.

Funkcja znalezienie klucza kolumny:

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

Oto pełna testów jednostkowych do tego:

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

Mogłeś zwróci Map jak wyżej, lub listę Pair dla nazwę kolumny do mapowania ustawień, ponieważ nazwa kolumny nie znajduje się w jej ustawieniach.

Można również zmienić konfigurację pliku konfiguracyjnego, aby ułatwić przetwarzanie konfiguracji (np. Nazwa tabeli w obiekcie konfiguracyjnym, a nie jako lewy klawisz. do obiektu, a nie jako lewy klawisz.)