Crystal-lang: JSON đệ quy hoặc Hash

Sep 04 2020

Tôi đang cố tạo JSON hoặc Hash có thể có độ sâu N. Ví dụ: X những người có tên duy nhất có thể có Y trẻ và những trẻ đó có thể có Z trẻ (và tiếp tục cho đến N thế hệ). Tôi muốn tạo một Hash (hoặc JSON) trông giống như sau:

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

Tôi đã thử làm việc với bí danh đệ quy nhưng không thể đạt được điều đó.

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

Đầu vào có thể đến từ các mảng Chuỗi như

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

(Tôi có thể giải quyết các vòng lặp. Vấn đề của tôi là thêm chúng vào Hash vì kích thước của chúng không xác định.)

Tôi đã xem qua cuộc thảo luận này https://forum.crystal-lang.org/t/how-do-i-create-a-nested-hash-type/885 nhưng tiếc là tôi không thể đạt được những gì tôi muốn và cũng giữ các phương thức của Hash (hoặc JSON) (cần thiết).

Trả lời

1 JonneHaß Sep 04 2020 at 17:12

Tôi không thể xác định rõ làm thế nào bạn đến được kết quả ví dụ từ đầu vào ví dụ của bạn, vì vậy tôi sẽ sử dụng một thiết lập khác: Giả sử chúng ta có một định dạng tệp cấu hình đơn giản trong đó các khóa được cấu trúc và nhóm thông qua một chuỗi chấm và tất cả các giá trị luôn là chuỗi.

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

Chúng ta có thể phân tích cú pháp nó thành một đệ quy Hashnhư vậy:

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

Đầu ra sẽ là:

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

Ngoài ra, chúng ta có thể phân tích cú pháp nó thành một cấu trúc đệ quy:

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

Đầu ra sau đó sẽ là:

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

Cấu trúc đệ quy hiện tại ít lỗi hơn một chút, cung cấp một vị trí tốt cho các phương thức tùy chỉnh trên các đối tượng miền được phân tích cú pháp của bạn và thường có một tương lai chắc chắn hơn so với bí danh đệ quy, đôi khi có một chút lỗi.

Ví dụ đầy đủ trên carc.in: https://carc.in/#/r/9mxr