我有一个响应模式的swagger(openAPI)定义:
h = { "type"=>"object",
"properties"=>{
"books"=>{
"type"=>"array",
"items"=>{
"type"=>"object",
"properties"=>{
"urn" =>{ "type"=>"string" },
"title"=>{ "type"=>"string" }
}
}
}
}
}
并希望将其转换为以下格式,以便能够将此响应显示为树:
{ "name"=>"200",
"children"=> [
{
"name"=>"books (array)",
"children"=> [
{"name"=>"urn (string)" },
{"name"=>"title (string)" }
]
}
]
}
在swagger模式格式中,节点可以是对象(具有属性),也可以是项目数组,它们本身就是对象。这是我编写的函数:schema参数是上面显示的swagger格式的散列,而tree变量包含{name: "200"}
def build_tree(schema, tree)
if schema.class == ActiveSupport::HashWithIndifferentAccess
case schema[:type]
when 'object'
tree[:children] = []
schema[:properties].each do |property_name, property_schema|
tree[:children] <<
{ name: property_name, children: build_tree(property_schema, tree) }
end
when 'array'
schema[:items].each do |property_name, property_schema|
tree[:children] <<
{ name: property_name, children: build_tree(property_schema, tree) }
end
when nil
tree[:name] == schema
end
else
tree[:name] == schema
end
end
不幸的是我觉得我在某个地方犯了错误,因为这会返回以下Hash:
{ :name=>"200",
:children=>[
{ :name=>"type", :children=>false },
{ :name=>"properties", :children=>false },
{ :name=>"books",
:children=>{
"type"=>"object",
"properties"=>{
"urn"=>{"type"=>"string"},
"title"=>{"type"=>"string"}
}
}
}
]
}
我必须错过递归中的一步或以错误的方式传递树,但我担心我没有足够的脑力来弄清楚:)也许是一个善良的灵魂与写美丽的红宝石的礼物代码会帮我一把!
答案 0 :(得分:1)
递归!尝试Elixir: - )
要跟踪递归方法,我会写出大量puts
并添加一个级别编号。
因为我没有Rails,所以我删除了Rails的东西。通过这种轻微的修改,您的输入(书籍数组不是数组!)和您的代码:
schema =
{ "type"=>"object",
"properties"=>{
"books"=>{
"type"=>"array",
"items"=>{
"type"=>"object",
"properties"=>{
"urn" =>{ "type"=>"string" },
"title"=>{ "type"=>"string" }
}
}
}
}
}
tree = {}
def build_tree(schema, tree, level)
puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
case schema['type']
when 'object'
puts "in when object for #{schema['properties'].size} properties :"
i = 0
schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
tree[:children] = []
schema['properties'].each do | property_name, property_schema |
puts "level=#{level} property_name=#{property_name}"
tree[:children] << { name: property_name, children: build_tree(property_schema, tree, level + 1) }
end
when 'array'
puts "in when array for #{schema['items'].size} items will process following items :"
i = 0
schema['items'].each_key{ | name | puts "#{i+=1}. #{name}" }
schema['items'].each do | property_name, property_schema |
puts "level=#{level} property_name=#{property_name}, property_schema=#{property_schema.inspect}"
tree[:children] << { name: property_name, children: build_tree(property_schema, tree, level + 1) }
end
when nil
puts "in when nil"
tree[:name] == schema
end
end
build_tree(schema, tree, 1)
puts tree
结果就是你得到的:
$ ruby -w t_a.rb
level=1 schema[:type]="object", schema class is Hash
in when object for 1 properties :
1. books
level=1 property_name=books
level=2 schema[:type]="array", schema class is Hash
in when array for 2 items will process following items :
1. type
2. properties
level=2 property_name=type, property_schema="object"
level=3 schema[:type]=nil, schema class is String
in when nil
level=2 property_name=properties, property_schema={"urn"=>{"type"=>"string"}, "title"=>{"type"=>"string"}}
level=3 schema[:type]=nil, schema class is Hash
in when nil
{:children=>[
{ :name=>"type", :children=>false},
{ :name=>"properties", :children=>false},
{ :name=>"books",
:children=>{
"type"=>"object",
"properties"=>{
"urn"=>{"type"=>"string"},
"title"=>{"type"=>"string"}
}
}
}
]
}
(注意:我手动打印了生成的树)。
跟踪显示正在发生的事情:在when 'array'
编写schema['items'].each
时,您可能希望迭代多个项目。但是没有项目,只有一个哈希。所以schema['items'].each
会迭代密钥。然后,您使用没有'type'
键的模式重复,因此case schema['type']
属于when nil
。
请注意,如果递归调用when 'object'
而不是when nil
,则tree[:children] = []
会删除之前的结果,因为您始终使用相同的初始tree
。要堆叠中间结果,您需要在递归调用中提供新变量。
理解递归的最好方法不是循环到方法的开头,而是想象一连串的调用:
method_1
|
+------> method_2
|
+------> method_3
如果将与参数相同的初始参数传递给递归调用,则会被最后返回的值删除。但是如果你传递一个新变量,你可以在累积操作中使用它。
如果您检查过schema['items']
实际上是一个数组,就像我在解决方案中那样,您会看到输入与预期不符:
$ ruby -w t.rb
level=1 schema[:type]="object", schema class is Hash
in when object for 1 properties :
1. books
level=1 property_name=books
level=2 schema[:type]="array", schema class is Hash
in when array
oops ! Array expected
{:children=>[{:name=>"books", :children=>"oops ! Array expected"}]}
现在我的解决方案。我给你留下化妆品细节。
schema =
{ "type"=>"object",
"properties"=>{
"books"=>{
"type"=>"array",
"items"=> [ # <----- added [
{ "type"=>"object",
"properties" => {
"urn" => { "type"=>"string" },
"title" => { "type"=>"string" }
}
},
{ "type"=>"object",
"properties" => {
"urn2" => { "type"=>"string" },
"title2" => { "type"=>"string" }
}
}
] # <----- added ]
} # end books
} # end properties
} # end schema
tree = {"name"=>"200", children: []}
def build_tree(schema, tree, level)
puts
puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
puts "level=#{level} tree=#{tree}"
case schema['type']
when 'object'
puts "in when object for #{schema['properties'].size} properties :"
i = 0
schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
schema['properties'].each do | property_name, property_schema |
puts "object level=#{level}, property_name=#{property_name}"
type, sub_tree = build_tree(property_schema, {children: []}, level + 1)
puts "object level=#{level} after recursion, type=#{type} sub_tree=#{sub_tree}"
child = { name: property_name + type }
child[:children] = sub_tree unless sub_tree.empty?
tree[:children] << child
end
puts "object level=#{level} about to return tree=#{tree}"
tree
when 'array'
puts "in when array"
case schema['items']
when Array
puts "in when Array for #{schema['items'].size} items"
i = 0
items = []
schema['items'].each do | a_hash |
puts "item #{i+=1} has #{a_hash.keys.size} keys :"
a_hash.keys.each{ | key | puts key }
# if the item has "type"=>"object" and "properties"=>{ ... }, then
# the whole item must be passed as argument to the next recursion
puts "level=#{level} about to recurs for item #{i}"
answer = build_tree(a_hash, {children: []}, level + 1)
puts "level=#{level} after recurs, answer=#{answer}"
items << { "item #{i}" => answer }
end
return ' (array)', items
else
puts "oops ! Array expected"
"oops ! Array expected"
end
when 'string'
puts "in when string, schema=#{schema}"
return ' (string)', []
else
puts "in else"
tree[:name] == schema
end
end
build_tree(schema, tree, 1)
puts 'final result :'
puts tree
执行:
$ ruby -w t.rb
level=1 schema[:type]="object", schema class is Hash
level=1 tree={"name"=>"200", :children=>[]}
in when object for 1 properties :
1. books
object level=1, property_name=books
level=2 schema[:type]="array", schema class is Hash
level=2 tree={:children=>[]}
in when array
in when Array for 2 items
item 1 has 2 keys :
type
properties
level=2 about to recurs for item 1
level=3 schema[:type]="object", schema class is Hash
level=3 tree={:children=>[]}
in when object for 2 properties :
1. urn
2. title
object level=3, property_name=urn
level=4 schema[:type]="string", schema class is Hash
level=4 tree={:children=>[]}
in when string, schema={"type"=>"string"}
object level=3 after recursion, type= (string) sub_tree=[]
object level=3, property_name=title
level=4 schema[:type]="string", schema class is Hash
level=4 tree={:children=>[]}
in when string, schema={"type"=>"string"}
object level=3 after recursion, type= (string) sub_tree=[]
object level=3 about to return tree={:children=>[{:name=>"urn (string)"}, {:name=>"title (string)"}]}
level=2 after recurs, answer={:children=>[{:name=>"urn (string)"}, {:name=>"title (string)"}]}
item 2 has 2 keys :
type
properties
level=2 about to recurs for item 2
level=3 schema[:type]="object", schema class is Hash
level=3 tree={:children=>[]}
in when object for 2 properties :
1. urn2
2. title2
object level=3, property_name=urn2
level=4 schema[:type]="string", schema class is Hash
level=4 tree={:children=>[]}
in when string, schema={"type"=>"string"}
object level=3 after recursion, type= (string) sub_tree=[]
object level=3, property_name=title2
level=4 schema[:type]="string", schema class is Hash
level=4 tree={:children=>[]}
in when string, schema={"type"=>"string"}
object level=3 after recursion, type= (string) sub_tree=[]
object level=3 about to return tree={:children=>[{:name=>"urn2 (string)"}, {:name=>"title2 (string)"}]}
level=2 after recurs, answer={:children=>[{:name=>"urn2 (string)"}, {:name=>"title2 (string)"}]}
object level=1 after recursion, type= (array) sub_tree=[{"item 1"=>{:children=>[{:name=>"urn (string)"}, {:name=>"title (string)"}]}}, {"item 2"=>{:children=>[{:name=>"urn2 (string)"}, {:name=>"title2 (string)"}]}}]
object level=1 about to return tree={"name"=>"200", :children=>[{:name=>"books (array)", :children=>[{"item 1"=>{:children=>[{:name=>"urn (string)"}, {:name=>"title (string)"}]}}, {"item 2"=>{:children=>[{:name=>"urn2 (string)"}, {:name=>"title2 (string)"}]}}]}]}
final result :
{"name"=>"200", :children=>[{:name=>"books (array)", :children=>[{"item 1"=>{:children=>[{:name=>"urn (string)"}, {:name=>"title (string)"}]}}, {"item 2"=>{:children=>[{:name=>"urn2 (string)"}, {:name=>"title2 (string)"}]}}]}]}
编辑结果:
{"name"=>"200",
:children=>[
{
:name=>"books (array)",
:children=>[
{"item 1"=>{
:children=>[
{:name=>"urn (string)"},
{:name=>"title (string)"}
]
}
},
{"item 2"=>{
:children=>[
{:name=>"urn2 (string)"},
{:name=>"title2 (string)"}
]
}
}
]
}
]
}
答案 1 :(得分:1)
@BernardK建议的解决方案的长度令人印象深刻,但我无法让它发挥作用。这是我更谦虚的解决方案。我将它包装在一个类中,以便我可以正确地测试它。
您的代码存在的一个问题是,在某些地方,您将返回tree[:name] == schema
,其评估结果为false
。我认为您打算分配tree[:name] = schema
,然后返回tree
。
和@BernardK一样,我假设一个类型&#39;数组的模式&#39;作为它的价值,它将具有一系列的东西。如果这不是它的工作方式,那么请您提供一个示例,其中包括&#39; array&#39;不仅仅是对象&#39;周围的附加层?
希望在这个答案和另一个答案之间,你可以从中找到适合你的东西。
# swagger.rb
class Swagger
def self.build_tree(schema, tree)
if schema.class == ActiveSupport::HashWithIndifferentAccess
case schema['type']
when 'object'
tree['children'] = schema['properties'].map do |property_name, property_schema|
build_tree(property_schema, {'name' => property_name})
end
tree
when 'array'
schema['items'].map do |item|
build_tree(item, {'name' => "#{tree['name']} (array)"})
end
when 'string'
{'name' => "#{tree['name']} (string)"}
end
else
raise ArgumentError, "Expected a HashWithIndifferentAccess but got #{schema.class}: #{schema}"
end
end
end
这是spec文件:
# /spec/swagger_spec.rb
require_relative '../swagger'
describe Swagger do
describe '.build_tree' do
context 'when given a Hash whose type is string' do
let(:tree) { {"name" => "urn"} }
let(:schema) { {"type" => "string"}.with_indifferent_access }
let(:expected) { {"name" => "urn (string)"} }
it 'returns a Hash with "name" as the key and the tree value and its type as the value' do
expect(Swagger.build_tree(schema, tree)).to eq(expected)
end
end
context 'when given a simple schema' do
let(:tree) { {"name" => "200"} }
let(:schema) { {"type" => "object",
"properties" => {
"urn" => {"type" => "string"},
"title" => {"type" => "string"}
}}.with_indifferent_access }
let(:expected) { {"name" => "200",
"children" => [{"name" => "urn (string)"},
{"name" => "title (string)"}
]} }
it 'transforms the tree into swagger (openAPI) format' do
expect(Swagger.build_tree(schema, tree)).to eq(expected)
end
end
context 'when given a complicated schema' do
let(:tree) { {"name" => "200"} }
let(:schema) { {"type" => "object",
"properties" =>
{"books" =>
{"type" => "array",
"items" =>
[{"type" => "object",
"properties" =>
{"urn" => {"type" => "string"}, "title" => {"type" => "string"}}
}] # <-- added brackets
}
}
}.with_indifferent_access }
let(:expected) { {"name" => "200",
"children" =>
[[{"name" => "books (array)",
"children" => [{"name" => "urn (string)"}, {"name" => "title (string)"}]
}]]
} }
it 'transforms the tree into swagger (openAPI) format' do
expect(Swagger.build_tree(schema, tree)).to eq(expected)
end
end
context 'when given a schema that is not a HashWithIndifferentAccess' do
let(:tree) { {"name" => "200"} }
let(:schema) { ['random array'] }
it 'raises an error' do
expect { Swagger.build_tree(schema, tree) }.to raise_error ArgumentError
end
end
end
end
答案 2 :(得分:1)
因此,项目数组不是项目数组,而是子模式的属性数组。这是考虑到这一事实的新解决方案:
schema =
{ "type"=>"object",
"properties"=>{
"books"=>{
"type"=>"array",
"items"=> {
"type"=>"object",
"properties" => {
"urn" => { "type"=>"string" },
"title" => { "type"=>"string" }
}
} # end items
} # end books
} # end properties
} # end schema
tree = {"name"=>"200"}
def build_tree(schema, tree, level)
puts
puts "level=#{level} schema[:type]=#{schema['type'].inspect}, schema class is #{schema.class}"
puts "level=#{level} tree=#{tree}"
case schema['type']
when 'object'
puts "in when object for #{schema['properties'].size} properties :"
i = 0
schema['properties'].each_key{ | name | puts "#{i+=1}. #{name}" }
tree[:children] = []
schema['properties'].each do | property_name, property_schema |
puts "object level=#{level}, property_name=#{property_name}"
type, sub_tree = build_tree(property_schema, {}, level + 1)
puts "object level=#{level} after recursion, type=#{type} sub_tree=#{sub_tree}"
child = { name: property_name + type }
sub_tree.each { | k, v | child[k] = v }
tree[:children] << child
end
puts "object level=#{level} about to return tree=#{tree}"
tree
when 'array'
puts "in when array"
case schema['items']
when Hash
puts "in when Hash"
puts "the schema has #{schema['items'].keys.size} keys :"
schema['items'].keys.each{ | key | puts key }
# here you could raise an error if the two keys are NOT "type"=>"object" and "properties"=>{ ... }
puts "Hash level=#{level} about to recurs"
return ' (array)', build_tree(schema['items'], {}, level + 1)
else
puts "oops ! Hash expected"
"oops ! Hash expected"
end
when 'string'
puts "in when string, schema=#{schema}"
return ' (string)', {}
else
puts "in else"
tree[:name] == schema # ???? comparison ?
end
end
build_tree(schema, tree, 1)
puts 'final result :'
puts tree
编辑结果(使用ruby 2.3.3p222测试):
{ "name"=>"200",
:children=> [
{
:name=>"books (array)",
:children=> [
{:name=>"urn (string)"},
{:name=>"title (string)"}
]
}
]
}
不要把它当作精彩的代码。我在每次12级地震中编写Ruby代码。目的是解释在代码中不起作用的内容,并引起注意在递归调用中使用新变量(现在是一个空哈希)。有很多案例需要测试并引发错误。
正确的方式是BDD和@moveson一样:首先为所有情况编写RSpec测试,特别是边缘情况,然后编写代码。我知道它给人的感觉太慢,但从长远来看,它会支付并取代调试和打印痕迹。
有关测试的更多信息
此代码很脆弱:例如,如果类型键未与属性键关联,则它将在schema['properties'].each
:undefined method 'each' for nil:NilClass
处失败。一个规格就好
context 'when a type object has no properties' do
let(:schema) { {"type" => "object", "xyz" => ...
有助于添加代码来检查前置条件。我也懒惰地使用RSpec来制作小脚本,但是为了认真开发,我努力,因为我已经认识到了它的好处。在调试中花费的时间将永远丢失,投入到规范中的时间可以在发生更改时提供安全性,并提供关于代码执行或不执行的清晰易读的报告。我推荐全新的Rspec 3 book。
关于访问哈希的另一个词:如果你有混合的字符串和符号,那就是问题的根源。
some_key = some_data # sometimes string, sometimes symbol
schema[some_key]...
如果内部键与外部数据的类型不同,将找不到该元素。在创建哈希时选择一种类型,例如符号,并系统地将访问变量转换为符号:
some_key = some_data # sometimes string, sometimes symbol
schema[some_key.to_sym]...
或全部为字符串:
some_key = some_data # sometimes string, sometimes symbol
schema[some_key.to_s]...