Crystal如何使用宏在编译时从文件生成代码加载CSV数据
Crystal编译时加载CSV数据为常量实现方案
核心问题原因
Crystal的{{ run <path> }}宏的运行机制是:编译阶段执行指定的Crystal脚本,仅将脚本标准输出的内容作为Crystal源代码片段插入到宏调用的位置,不会直接传递脚本运行时的内存对象。
你最初的脚本直接构造了OpenData::Country数组但没有输出任何内容,所以宏位置等效于没有任何代码,才会触发得到Nil的类型不匹配报错。
正确实现
1. 代码生成脚本(./sources/country_codes.cr)
你修改后的版本逻辑是正确的,补充特殊字符转义处理的优化版如下:
require "csv" content = File.read "#{__DIR__}/country-codes.csv" instances = CSV.new(content, headers: true).map do |row| # 处理字段内的双引号避免生成的代码出现语法错误 code = row["CountryCode"].gsub('"', '\\"') name = row["CountryName"].gsub('"', '\\"') %[Country.new("#{code}", "#{name}")] end puts "[#{instances.join(", ")}]"
2. 主代码中加载
module OpenData class Country def initialize(@code : String, @name : String) end end @@countries : Array(Country) = {{ run "./sources/country_codes.cr" }} end
编译阶段展开后,宏位置会直接替换为脚本输出的数组字面量,等效于你手动写了:
@@countries : Array(Country) = [Country.new("AD", "Andorra"), Country.new("AE", "United Arab Emirates"), ...]
扩展说明
如果你有大量同类CSV需要处理,可以把代码生成逻辑封装为通用工具,传入CSV路径、类名、字段映射规则即可自动生成对应代码,不用为每个CSV单独写生成脚本。
内容的提问来源于stack exchange,提问作者Maximilian Stroh
相关产品推荐
相关产品推荐

