Crystal-lang: Özyinelemeli JSON veya Hash

Sep 04 2020

N derinliğine sahip bir JSON veya Hash oluşturmaya çalışıyorum. Örnek: Benzersiz isimleri olan X kişinin Y çocuğu olabilir ve bu çocukların Z çocukları olabilir (ve N nesile kadar devam eder). Şuna benzeyen bir Hash (veya JSON) oluşturmak istiyorum:

{
  "John" => {
              "Lara" => { 
                          "Niko" => "Doe"
                        },
              "Kobe" => "Doe"
            },
  "Jess" => {
              "Alex" => "Patrik"
            }
}

Yinelemeli takma adlarla çalışmayı denedim ama bunu başaramadım.

alias Person = Hash(String, Person) | Hash(String, String)

Giriş, String dizilerinden gelebilir.

["John|Lara|Niko", "John|Kobe", "Jess|Alex"]
["Doe", "Patrik"]

(Döngülerle başa çıkabilirim. Sorunum, boyutları bilinmediği için onları Hash'e eklemek.)

Bu tartışmaya rastladım https://forum.crystal-lang.org/t/how-do-i-create-a-nested-hash-type/885 ancak ne yazık ki istediğimi elde edemiyorum ve ayrıca Hash'in (veya JSON'un) yöntemlerini (gerekli olan) tutamıyorum.

Yanıtlar

1 JonneHaß Sep 04 2020 at 17:12

Örnek girişinizden örnek sonucunuza nasıl ulaştığınızı tam olarak anlayamadım, bu yüzden farklı bir kurulum kullanacağım: Anahtarların noktalı bir sırayla yapılandırıldığı ve gruplandığı basit bir yapılandırma dosyası biçimimiz olduğunu varsayalım ve tüm değerler her zaman dizelerdir.

app.name = test
app.mail.enable = true
app.mail.host = mail.local
server.host = localhost
server.port = 3000
log_level = debug

Bunu şöyle bir özyinelemeli olarak ayrıştırabiliriz Hash:

alias ParsedConfig = Hash(String, ParsedConfig)|String

config = Hash(String, ParsedConfig).new

# CONFIG being our input from above
CONFIG.each_line do |entry|
  keys, value = entry.split(" = ")
  keys = keys.split(".")
  current = config
  keys[0..-2].each do |key|
    if current.has_key?(key)
      item = current[key]
      if item.is_a?(Hash)
        current = item
      else
        raise "Malformed config"
      end
    else
      item = Hash(String, ParsedConfig).new
      current[key] = item
      current = item
    end
  end

  current[keys.last] = value
end

pp! config

Çıktı şu şekilde olacaktır:

config # => {"app" =>
  {"name" => "test", "mail" => {"enable" => "true", "host" => "mail.local"}},
 "server" => {"host" => "localhost", "port" => "3000"},
 "log_level" => "debug"}

Alternatif olarak, onu özyinelemeli bir yapıya ayrıştırabiliriz:

record ConfigGroup, entries = Hash(String, ConfigGroup|String).new

config = ConfigGroup.new

# CONFIG being our input from above
CONFIG.each_line do |entry|
  keys, value = entry.split(" = ")
  keys = keys.split(".")
  current = config
  keys[0..-2].each do |key|
    if current.entries.has_key?(key)
      item = current.entries[key]
      if item.is_a?(ConfigGroup)
        current = item
      else
        raise "Malformed config"
      end
    else
      item = ConfigGroup.new
      current.entries[key] = item
      current = item
    end
  end

  current.entries[keys.last] = value
end

pp! config

Çıktı o zaman şöyle olacaktır:

config # => ConfigGroup(
 @entries=
  {"app" =>
    ConfigGroup(
     @entries=
      {"name" => "test",
       "mail" =>
        ConfigGroup(@entries={"enable" => "true", "host" => "mail.local"})}),
   "server" => ConfigGroup(@entries={"host" => "localhost", "port" => "3000"}),
   "log_level" => "debug"})

Özyinelemeli yapılar şu anda biraz daha az hatalıdır, ayrıştırılmış etki alanı nesnelerinizdeki özel yöntemler için güzel bir yer sunar ve genellikle bazen biraz hatalı olan özyinelemeli takma adlardan daha belirli bir geleceğe sahiptir.

Carc.in ile ilgili tam örnek: https://carc.in/#/r/9mxr