Как создать хэш из вложенного CSV в Ruby? - PullRequest
0 голосов
/ 28 октября 2019

У меня есть CSV в следующем формате:

name,contacts.0.phone_no,contacts.1.phone_no,codes.0,codes.1
YK,1234,4567,AB001,AK002

Как видите, это вложенная структура. CSV может содержать несколько строк. Я хотел бы преобразовать это в массив хэшей как это:

[
  {
    name: 'YK',
    contacts: [
        {
            phone_no: '1234'
        },
        {
            phone_no: '4567'
        }
    ],
    codes: ['AB001', 'AK002']
  }
]

Структура использует числа в данном формате для представления массивов. Внутри массивов могут быть хэши. Есть ли простой способ сделать это в Ruby?

Заголовки CSV являются динамическими. Это может измениться. Мне придется создавать хэш на лету на основе файла CSV.

Существует похожая библиотека узлов с именем csvtojson , которая делает это для JavaScript.

Ответы [ 2 ]

1 голос
/ 28 октября 2019

Давайте сначала создадим файл CSV.

str = <<~END
name,contacts.0.phone_no,contacts.1.phone_no,codes.0,IQ,codes.1
YK,1234,4567,AB001,173,AK002
ER,4321,7654,BA001,81,KA002
END

FName = 't.csv'

File.write(FName, str)
  #=> 121

Iсоздали вспомогательный метод для построения шаблона , который будет использоваться для преобразования каждой строки файла CSV (после первой, содержащей заголовки) в элемент (хеш) нужного массива.

require 'csv'

def construct_pattern(csv)
  csv.headers.group_by { |col| col[/[^.]+/] }.
      transform_values do |arr|
        case arr.first.count('.')
        when 0
          arr.first
        when 1
          arr
        else 
          key = arr.first[/(?<=\d\.).*/]
          arr.map { |v| { key=>v } }
        end
      end
end

В приведенном ниже коде для рассматриваемого примера:

construct_pattern(csv)
  #=> {"name"=>"name",
  #    "contacts"=>[{"phone_no"=>"contacts.0.phone_no"},
  #                 {"phone_no"=>"contacts.1.phone_no"}],
  #    "codes"=>["codes.0", "codes.1"],
  #    "IQ"=>"IQ"}

Путем привязки if pattern.empty? к приведенному выше выражению мы гарантируем, что шаблон создается только один раз.

Теперь мы можем создать нужный массив.

pattern = {}
CSV.foreach(FName, headers: true).map do |csv|
  pattern = construct_pattern(csv) if pattern.empty?
  pattern.each_with_object({}) do |(k,v),h|
    h[k] =
    case v
    when Array
      case v.first
      when Hash
        v.map { |g| g.transform_values { |s| csv[s] } }
      else
        v.map { |s| csv[s] }
      end
    else
      csv[v]
    end
  end
end
  #=> [{"name"=>"YK",
  #     "contacts"=>[{"phone_no"=>"1234"}, {"phone_no"=>"4567"}],
  #     "codes"=>["AB001", "AK002"],
  #     "IQ"=>"173"},
  #    {"name"=>"ER",
  #     "contacts"=>[{"phone_no"=>"4321"}, {"phone_no"=>"7654"}],
  #     "codes"=>["BA001", "KA002"],
  #     "IQ"=>"81"}] 

Методы CSV, которые я использовал, описаны в CSV . См. Также Enumerable # group_by и Hash # transform_values ​​.

1 голос
/ 28 октября 2019

Просто прочитайте и проанализируйте его построчно. Переменная arr в приведенном ниже коде будет содержать массив Hash, который вам нужен

arr = []

File.readlines('README.md').drop(1).each do |line|
  fields = line.split(',').map(&:strip)

  hash = { name: fields[0], contacts: [fields[1], fields[2]], address: [fields[3], fields[4]] }
  arr.push(hash)
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...