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\"]"]