Crystal-lang: JSON récursif ou Hash

Sep 04 2020

J'essaye de créer un JSON ou un Hash qui peut avoir N profondeur. Exemple: X personnes avec des noms uniques peuvent avoir Y enfants et ces enfants peuvent avoir Z enfants (et cela continue jusqu'à N générations). Je veux créer un Hash (ou JSON) qui ressemblerait à ceci:

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

J'ai essayé de travailler avec des alias récursifs mais je n'y suis pas parvenu.

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

L'entrée peut provenir de tableaux de String comme

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

(Je peux gérer les boucles. Mon problème est de les ajouter au hachage car leur taille est inconnue.)

Je suis tombé sur cette discussion https://forum.crystal-lang.org/t/how-do-i-create-a-nested-hash-type/885 mais malheureusement je ne peux pas réaliser ce que je veux et aussi garder les méthodes de Hash (ou JSON) (qui sont nécessaires).

Réponses

1 JonneHaß Sep 04 2020 at 17:12

Je ne pouvais pas tout à fait comprendre comment vous êtes arrivé à votre résultat d'exemple à partir de votre entrée d'exemple, donc je vais utiliser une configuration différente: supposons que nous ayons un format de fichier de configuration simple où les clés sont structurées et regroupées dans une séquence en pointillés toutes les valeurs sont toujours des chaînes.

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

Nous pouvons l'analyser en un récursif Hashcomme ceci:

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

La sortie sera:

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

Alternativement, nous pouvons l'analyser en une structure récursive:

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

La sortie sera alors:

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

Les structures récursives sont actuellement un peu moins boguées, offrent un bon emplacement pour les méthodes personnalisées sur vos objets de domaine analysés et ont généralement un avenir plus certain que les alias récursifs, qui sont parfois un peu bogués.

Exemple complet sur carc.in: https://carc.in/#/r/9mxr