【发布时间】:2010-12-18 07:29:45
【问题描述】:
假设我有字符串
"[1,2,[3,4,[5,6]],7]"
如何将其解析到数组中
[1,2,[3,4,[5,6]],7]
?
在我的用例中,嵌套结构和模式是完全任意的。
我目前的临时解决方案包括在每个句点后添加一个空格并使用YAML.load,但如果可能的话,我希望有一个更干净的。
(如果可能,不需要外部库)
【问题讨论】:
假设我有字符串
"[1,2,[3,4,[5,6]],7]"
如何将其解析到数组中
[1,2,[3,4,[5,6]],7]
?
在我的用例中,嵌套结构和模式是完全任意的。
我目前的临时解决方案包括在每个句点后添加一个空格并使用YAML.load,但如果可能的话,我希望有一个更干净的。
(如果可能,不需要外部库)
【问题讨论】:
正在使用JSON 正确解析该特定示例:
s = "[1,2,[3,4,[5,6]],7]"
#=> "[1,2,[3,4,[5,6]],7]"
require 'json'
#=> true
JSON.parse s
#=> [1, 2, [3, 4, [5, 6]], 7]
如果这不起作用,您可以尝试通过eval 运行字符串,但您必须确保没有传递任何实际的ruby 代码,因为eval 可能被用作注入漏洞。
编辑:这是一个简单的递归,基于正则表达式的解析器,没有验证,没有测试,不用于生产等:
def my_scan s
res = []
s.scan(/((\d+)|(\[(.+)\]))/) do |match|
if match[1]
res << match[1].to_i
elsif match[3]
res << my_scan(match[3])
end
end
res
end
s = "[1,2,[3,4,[5,6]],7]"
p my_scan(s).first #=> [1, 2, [3, 4, [5, 6]], 7]
【讨论】:
JSON.parse 恕我直言。
JSON 至少在 1.9.x 中是 Ruby 核心库的一部分。
s = "['hello', 2, 'test', 5.0]" 这样的多类型数组,JSON 将无法解析并出现一般错误unexpected token at ...。但是,YAML 确实如 @Arup 的 answer: YAML.load(s) => ["hello", 2, "test", 5.0] 所示工作。
同样可以使用 Ruby 标准 libaray YAML 来完成,如下所示:
require 'yaml'
s = "[1,2,[3,4,[5,6]],7]"
YAML.load(s)
# => [1, 2, [3, 4, [5, 6]], 7]
【讨论】:
"['hello', 2, 'test', 5.0]",其中 JSON 无法解析。
“显然”最好的解决方案是编写自己的解析器。 [如果你喜欢编写解析器,以前从未做过并想学习新东西,或者想控制确切的语法]
require 'parslet'
class Parser < Parslet::Parser
rule(:space) { str(' ') }
rule(:space?) { space.repeat(0) }
rule(:openbrace_) { str('[').as(:op) >> space? }
rule(:closebrace_) { str(']').as(:cl) >> space? }
rule(:comma_) { str(',') >> space? }
rule(:integer) { match['0-9'].repeat(1).as(:int) }
rule(:value) { (array | integer) >> space? }
rule(:list) { value >> ( comma_ >> value ).repeat(0) }
rule(:array) { (openbrace_ >> list.maybe.as(:list) >> closebrace_ )}
rule(:nest) { space? >> array.maybe }
root(:nest)
end
class Arr
def initialize(args)
@val = args
end
def val
@val.map{|v| v.is_a?(Arr) ? v.val : v}
end
end
class MyTransform < Parslet::Transform
rule(:int => simple(:x)) { Integer(x) }
rule(:op => '[', :cl => ']') { Arr.new([]) }
rule(:op => '[', :list => simple(:x), :cl => ']') { Arr.new([x]) }
rule(:op => '[', :list => sequence(:x), :cl => ']') { Arr.new(x) }
end
def parse(s)
MyTransform.new.apply(Parser.new.parse(s)).val
end
parse " [ 1 , 2 , [ 3 , 4 , [ 5 , 6 , [ ]] ] , 7 ] "
Parslet 转换会将单个值匹配为“简单”,但如果该值返回一个数组,您很快就会得到数组的数组,那么您必须开始使用子树。然而,返回对象很好,因为它们在转换上面的层时代表单个值......所以序列将匹配得很好。
加上返回裸数组的麻烦,以及 Array([x]) 和 Array(x) 给你同样的东西的问题......你会得到非常混乱的结果。
为了避免这种情况,我创建了一个名为 Arr 的辅助类,它表示一个项目数组。 然后我可以决定我传递给它的内容。然后我可以让解析器保留所有括号,即使您有@MateuszFryc 调用的示例:)(感谢@MateuszFryc)
【讨论】:
rule(:op => '[', :cl => ']') { Arr.new([]) } 永远不会匹配?正如你所想的那样。这与rule(:op => '[', :list => simple(:x), :cl => ']') { Arr.new([x]) } 相当匹配,因此出现了问题。
rule(:op => '[', :list => simple(:x), :cl => ']') { Arr.new([x].compact) } 和您认为已使用的 rule(:op => '[', :cl => ']') { Arr.new([]) },完全删除。
使用评估
array = eval("[1,2,[3,4,[5,6]],7]")
【讨论】: