Ruby - (JSON) 用方法来阻止
Ruby - (JSON) with methods to block
我有一个 JSON 具有以下结构。
{
"methods": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
},
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
}
]
}
这个 JSON 被解析并且找到参数:
def parse_json(json)
methods = JSON.parse(json, , :symbolize_names => true)
methods.each do |options|
pass_method(options)
end
end
def pass_method(options)
argument_names = self.class.instance_method(options[:method].to_sym).parameters.map(&:last)
args = argument_names.map do |arg|
if arg == :options
options
else
options[arg] || ''
end
end
self.send(options[:method], *args)
end
现在我想将块传递给那些方法。
JSON 看起来像:
{
"methods": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value",
"block": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
},
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
}
]
}
]
}
我怎样才能完成这项工作?所以我可以将块传递给方法并在块中执行子方法?
我对你到底想做什么感到困惑,但我认为下面的代码可以做到(简化为与块相关的代码)。
免责声明:您所做的事情看起来既危险又多余。如果 JSON 来自不受信任的来源,您需要 非常 小心将允许的方法列入白名单,以免允许任意代码执行。我不相信自己会编写这段代码。如果来源是可信的,为什么不只是 eval
纯 Ruby 代码呢?
class A
def a
puts "a start"
yield
puts "a end"
end
def b; puts "b" end
def c; puts "c"; yield end
def d; puts "d" end
def call(spec)
spec.each do |m|
# Use #fetch because this is required
method_name = m.fetch(:method)
block_spec = m[:block]
block = if block_spec
->(*args) {
# Recursively evaluate, meaning that block specs can themselves
# contain blocks!
call(block_spec)
}
else
# Using a default empty block is easier than not providing one and
# using conditionals to workaround it.
Proc.new{}
end
# WARNING: This is unsafe! If spec does not come a trusted source, this
# allows arbitrary code execution which is really bad.
send(method_name, &block)
end
end
end
spec = {
methods: [
{
method: "a",
block: [{
method: "b"
}, {
method: "c",
block: [{method: "d"}]
}]
}
]
}
A.new.call(spec[:methods])
输出:
a start
b
c
d
a end
嗯,首先,我认为您当前基于方法参数名称传递参数的方法有点奇怪。相反,为什么不将参数作为数组传递,类似于 Object#send
?
所需的形式
{
"method": "push",
"arguments": [
1, "some_text", {"key": "value"}, ["array"]
]
}
这大大简化了代码,并且可以使用散列传递关键字参数。
至于如何将块传递给方法,您可以通过构造一个 Proc 对象,然后使用 &block
语法将其传递给 send
方法来实现:
method = :inspect
args = [1, "some_text", {"key": "value"}, ["array"]]
block = proc{|x| x.send(method, *args) }
some_object.send(:map!, &block)
综合所有这些想法,我们得出以下解决方案:
json = <<-JSON
{
"methods": [
{
"method": "push",
"arguments": [
1, "some_text", {"key": "value"}, ["array"]
]
},
{
"method": "delete",
"arguments": ["some_text"]
},
{
"method": "map!",
"arguments": [],
"block": [
{
"method": "to_s",
"arguments": []
}
]
}
]
}
JSON
def to_call_proc(method)
method_name = method['method'] || ''
arguments = method['arguments'] || []
block = to_multi_call_proc(method['block']) if method.has_key? 'block'
if block
proc{|x| x.public_send(method_name, *arguments, &block) }
else
proc{|x| x.public_send(method_name, *arguments) }
end
end
def to_multi_call_proc(methods)
call_procs = methods.map(&method(:to_call_proc))
last_call_proc = call_procs.pop
proc do |x|
call_procs.each{|call_proc| call_proc.call(x)}
last_call_proc.call(x) if last_call_proc
end
end
def call_methods(receiver, methods)
to_multi_call_proc(methods).call(receiver)
end
require 'json'
a = []
call_methods(a, JSON.parse(json)['methods'])
p a
结果:
["1", "{\"key\"=>\"value\"}", "[\"array\"]"]
我有一个 JSON 具有以下结构。
{
"methods": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
},
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
}
]
}
这个 JSON 被解析并且找到参数:
def parse_json(json)
methods = JSON.parse(json, , :symbolize_names => true)
methods.each do |options|
pass_method(options)
end
end
def pass_method(options)
argument_names = self.class.instance_method(options[:method].to_sym).parameters.map(&:last)
args = argument_names.map do |arg|
if arg == :options
options
else
options[arg] || ''
end
end
self.send(options[:method], *args)
end
现在我想将块传递给那些方法。 JSON 看起来像:
{
"methods": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value",
"block": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
},
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
}
]
}
]
}
我怎样才能完成这项工作?所以我可以将块传递给方法并在块中执行子方法?
我对你到底想做什么感到困惑,但我认为下面的代码可以做到(简化为与块相关的代码)。
免责声明:您所做的事情看起来既危险又多余。如果 JSON 来自不受信任的来源,您需要 非常 小心将允许的方法列入白名单,以免允许任意代码执行。我不相信自己会编写这段代码。如果来源是可信的,为什么不只是 eval
纯 Ruby 代码呢?
class A
def a
puts "a start"
yield
puts "a end"
end
def b; puts "b" end
def c; puts "c"; yield end
def d; puts "d" end
def call(spec)
spec.each do |m|
# Use #fetch because this is required
method_name = m.fetch(:method)
block_spec = m[:block]
block = if block_spec
->(*args) {
# Recursively evaluate, meaning that block specs can themselves
# contain blocks!
call(block_spec)
}
else
# Using a default empty block is easier than not providing one and
# using conditionals to workaround it.
Proc.new{}
end
# WARNING: This is unsafe! If spec does not come a trusted source, this
# allows arbitrary code execution which is really bad.
send(method_name, &block)
end
end
end
spec = {
methods: [
{
method: "a",
block: [{
method: "b"
}, {
method: "c",
block: [{method: "d"}]
}]
}
]
}
A.new.call(spec[:methods])
输出:
a start
b
c
d
a end
嗯,首先,我认为您当前基于方法参数名称传递参数的方法有点奇怪。相反,为什么不将参数作为数组传递,类似于 Object#send
?
{
"method": "push",
"arguments": [
1, "some_text", {"key": "value"}, ["array"]
]
}
这大大简化了代码,并且可以使用散列传递关键字参数。
至于如何将块传递给方法,您可以通过构造一个 Proc 对象,然后使用 &block
语法将其传递给 send
方法来实现:
method = :inspect
args = [1, "some_text", {"key": "value"}, ["array"]]
block = proc{|x| x.send(method, *args) }
some_object.send(:map!, &block)
综合所有这些想法,我们得出以下解决方案:
json = <<-JSON
{
"methods": [
{
"method": "push",
"arguments": [
1, "some_text", {"key": "value"}, ["array"]
]
},
{
"method": "delete",
"arguments": ["some_text"]
},
{
"method": "map!",
"arguments": [],
"block": [
{
"method": "to_s",
"arguments": []
}
]
}
]
}
JSON
def to_call_proc(method)
method_name = method['method'] || ''
arguments = method['arguments'] || []
block = to_multi_call_proc(method['block']) if method.has_key? 'block'
if block
proc{|x| x.public_send(method_name, *arguments, &block) }
else
proc{|x| x.public_send(method_name, *arguments) }
end
end
def to_multi_call_proc(methods)
call_procs = methods.map(&method(:to_call_proc))
last_call_proc = call_procs.pop
proc do |x|
call_procs.each{|call_proc| call_proc.call(x)}
last_call_proc.call(x) if last_call_proc
end
end
def call_methods(receiver, methods)
to_multi_call_proc(methods).call(receiver)
end
require 'json'
a = []
call_methods(a, JSON.parse(json)['methods'])
p a
结果:
["1", "{\"key\"=>\"value\"}", "[\"array\"]"]