如何递归地从 Ruby 中的 json 结构的键中删除一个或多个字符
How to remove a character or characters from the keys of a json structure in Ruby recursively
所以基本上有这样的结构:
{
"i$tems": {
"it$em": [
{
"batt$ers": {
"ba$tter": [
{
"i$d": "1001",
"t$ype": "Regular"
},
{
"i$d": "1002",
"type": "Chocolate"
},
{
"i$d": "1003",
"t$ype": "Blueberry"
},
{
"i$d": "1004",
"t$ype": "Devil's Food"
}
]
},
"$id": "0001",
"$name": "Cake",
"$ppu": 0.55,
"$topping": [
{
"i$d": "5001",
"t$ype": "None"
},
{
"i$d": "5002",
"t$ype": "Glazed"
},
{
"i$d": "5005",
"t$ype": "Sugar"
},
{
"i$d": "5007",
"t$ype": "Powdered Sugar"
},
{
"i$d": "5006",
"t$ype": "Chocolate with Sprinkles"
},
{
"i$d": "5003",
"t$ype": "Chocolate"
},
{
"i$d": "5004",
"t$ype": "Maple"
}
],
"ty$pe": "donut"
}
]
}
}
并想要这样的东西:
{
"items": {
"item": [
{
"batters": {
"batter": [
{
"id": "1001",
"type": "Regular"
},
{
"id": "1002",
"type": "Chocolate"
},
{
"id": "1003",
"type": "Blueberry"
},
{
"id": "1004",
"type": "Devil's Food"
}
]
},
"id": "0001",
"name": "Cake",
"ppu": 0.55,
"topping": [
{
"id": "5001",
"type": "None"
},
{
"id": "5002",
"type": "Glazed"
},
{
"id": "5005",
"type": "Sugar"
},
{
"id": "5007",
"type": "Powdered Sugar"
},
{
"id": "5006",
"type": "Chocolate with Sprinkles"
},
{
"id": "5003",
"type": "Chocolate"
},
{
"id": "5004",
"type": "Maple"
}
],
"type": "donut"
}
]
}
}
在Ruby
基本上我想出了这个代码:
#!/usr/bin/env ruby
require 'yaml'
require 'json'
def iterate(data)
if data.is_a?(Array)
return data.map { |i| iterate(i) }
elsif data.is_a?(Hash)
new_h = Hash[data]
data.each do |k, v|
if k =~ /$/
new_key = k.delete("$")
new_h.delete(k)
if v.is_a?(Array) || v.is_a?(Hash)
new_h[new_key] = iterate(v)
else
new_h[new_key] = v
end
else
if v.is_a?(Array) || v.is_a?(Hash)
new_h[k] = iterate(v)
else
new_h[k] = v
end
end
end
return new_h
end
end
data = YAML.load(File.read('nested.json'))
puts JSON.dump(iterate(data))
这是一个通用的 map_keys
函数,它在哈希的键上递归地运行一些块。
def map_keys(hash, &blk)
hash.each_with_object({}) do |(key, val), new_hash|
new_hash[blk.call(key)] = if val.is_a?(Hash)
map_keys(val, &blk)
else
val
end
end
end
它returns 一个新的散列,修改了键。在您的情况下,您可以这样使用它:
new_data = map_keys(data) do |key|
key.gsub("$", '')
end
正在编辑
刚刚注意到您的数据中有数组。这是处理该问题的修改版本:
def map_keys(hash_or_array, &blk)
if hash_or_array.is_a?(Hash)
hash = hash_or_array
hash.each_with_object({}) do |(key, val), new_hash|
new_hash[blk.call(key)] = if [Array, Hash].include?(val.class)
map_keys(val, &blk)
else
val
end
end
elsif hash_or_array.is_a?(Array)
array = hash_or_array
array.map { |elem| map_keys(elem, &blk) }
end
end
顺便说一句,修改它以处理值而不是键很容易:
def map_values(hash_or_array, &blk)
if hash_or_array.is_a?(Hash)
hash = hash_or_array
hash.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = if [Array, Hash].include?(val.class)
map_keys(val, &blk)
else
blk.call val
end
end
elsif hash_or_array.is_a?(Array)
array = hash_or_array
array.map { |elem| map_keys(elem, &blk) }
end
end
如果唯一要删除的美元符号字符是键名称中的美元符号字符,无论是符号还是字符串,您可以执行以下操作。
def clean_up(obj)
case obj
when Hash
obj.each_with_object({}) do |(k,v),h|
key =
case k
when String then k.delete('$')
when Symbol then k.to_s.delete('$').to_sym
else k
end
h[key] = clean_up(v)
end
when Array
obj.each_with_object([]) { |e,a| a << clean_up(e) }
else
obj
end
end
假设
h =
{ "i$tems": {
"it$em": [
{ "batt$ers": {
"ba$tter": [
{ "i$d": "1001", "t$ype": "Regular" },
{ "i$d": "1002", type: "Choc$olate" }
]
},
"i$d": "0001",
42=>[
{ "i$d": "5001", "t$ype": "None" },
{ "str$ing"=>"5002", "t$ype": "Glazed"}
],
"ty$pe": "donut"
}
]
}
}
请注意,我简化了问题中的散列,还更改了一些键和值。那么,
clean_up h
#=> { :items=>{
:item=>[
{ :batters=>{
:batter=>[
{ :id=>"1001", :type=>"Regular" },
{ :id=>"1002", :type=>"Choc$olate" }
]
},
:id=>"0001",
42=>[
{ :id=>"5001", :type=>"None" },
{ "string"=>"5002", :type=>"Glazed" }
],
:type=>"donut"
}
]
}
}
如果保证(如示例中)唯一包含美元符号的符号或字符串是哈希键,则可以简化为以下内容。
require 'json'
JSON.parse(h.to_json.delete('$'))
所以基本上有这样的结构:
{
"i$tems": {
"it$em": [
{
"batt$ers": {
"ba$tter": [
{
"i$d": "1001",
"t$ype": "Regular"
},
{
"i$d": "1002",
"type": "Chocolate"
},
{
"i$d": "1003",
"t$ype": "Blueberry"
},
{
"i$d": "1004",
"t$ype": "Devil's Food"
}
]
},
"$id": "0001",
"$name": "Cake",
"$ppu": 0.55,
"$topping": [
{
"i$d": "5001",
"t$ype": "None"
},
{
"i$d": "5002",
"t$ype": "Glazed"
},
{
"i$d": "5005",
"t$ype": "Sugar"
},
{
"i$d": "5007",
"t$ype": "Powdered Sugar"
},
{
"i$d": "5006",
"t$ype": "Chocolate with Sprinkles"
},
{
"i$d": "5003",
"t$ype": "Chocolate"
},
{
"i$d": "5004",
"t$ype": "Maple"
}
],
"ty$pe": "donut"
}
]
}
}
并想要这样的东西:
{
"items": {
"item": [
{
"batters": {
"batter": [
{
"id": "1001",
"type": "Regular"
},
{
"id": "1002",
"type": "Chocolate"
},
{
"id": "1003",
"type": "Blueberry"
},
{
"id": "1004",
"type": "Devil's Food"
}
]
},
"id": "0001",
"name": "Cake",
"ppu": 0.55,
"topping": [
{
"id": "5001",
"type": "None"
},
{
"id": "5002",
"type": "Glazed"
},
{
"id": "5005",
"type": "Sugar"
},
{
"id": "5007",
"type": "Powdered Sugar"
},
{
"id": "5006",
"type": "Chocolate with Sprinkles"
},
{
"id": "5003",
"type": "Chocolate"
},
{
"id": "5004",
"type": "Maple"
}
],
"type": "donut"
}
]
}
}
在Ruby
基本上我想出了这个代码:
#!/usr/bin/env ruby
require 'yaml'
require 'json'
def iterate(data)
if data.is_a?(Array)
return data.map { |i| iterate(i) }
elsif data.is_a?(Hash)
new_h = Hash[data]
data.each do |k, v|
if k =~ /$/
new_key = k.delete("$")
new_h.delete(k)
if v.is_a?(Array) || v.is_a?(Hash)
new_h[new_key] = iterate(v)
else
new_h[new_key] = v
end
else
if v.is_a?(Array) || v.is_a?(Hash)
new_h[k] = iterate(v)
else
new_h[k] = v
end
end
end
return new_h
end
end
data = YAML.load(File.read('nested.json'))
puts JSON.dump(iterate(data))
这是一个通用的 map_keys
函数,它在哈希的键上递归地运行一些块。
def map_keys(hash, &blk)
hash.each_with_object({}) do |(key, val), new_hash|
new_hash[blk.call(key)] = if val.is_a?(Hash)
map_keys(val, &blk)
else
val
end
end
end
它returns 一个新的散列,修改了键。在您的情况下,您可以这样使用它:
new_data = map_keys(data) do |key|
key.gsub("$", '')
end
正在编辑
刚刚注意到您的数据中有数组。这是处理该问题的修改版本:
def map_keys(hash_or_array, &blk)
if hash_or_array.is_a?(Hash)
hash = hash_or_array
hash.each_with_object({}) do |(key, val), new_hash|
new_hash[blk.call(key)] = if [Array, Hash].include?(val.class)
map_keys(val, &blk)
else
val
end
end
elsif hash_or_array.is_a?(Array)
array = hash_or_array
array.map { |elem| map_keys(elem, &blk) }
end
end
顺便说一句,修改它以处理值而不是键很容易:
def map_values(hash_or_array, &blk)
if hash_or_array.is_a?(Hash)
hash = hash_or_array
hash.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = if [Array, Hash].include?(val.class)
map_keys(val, &blk)
else
blk.call val
end
end
elsif hash_or_array.is_a?(Array)
array = hash_or_array
array.map { |elem| map_keys(elem, &blk) }
end
end
如果唯一要删除的美元符号字符是键名称中的美元符号字符,无论是符号还是字符串,您可以执行以下操作。
def clean_up(obj)
case obj
when Hash
obj.each_with_object({}) do |(k,v),h|
key =
case k
when String then k.delete('$')
when Symbol then k.to_s.delete('$').to_sym
else k
end
h[key] = clean_up(v)
end
when Array
obj.each_with_object([]) { |e,a| a << clean_up(e) }
else
obj
end
end
假设
h =
{ "i$tems": {
"it$em": [
{ "batt$ers": {
"ba$tter": [
{ "i$d": "1001", "t$ype": "Regular" },
{ "i$d": "1002", type: "Choc$olate" }
]
},
"i$d": "0001",
42=>[
{ "i$d": "5001", "t$ype": "None" },
{ "str$ing"=>"5002", "t$ype": "Glazed"}
],
"ty$pe": "donut"
}
]
}
}
请注意,我简化了问题中的散列,还更改了一些键和值。那么,
clean_up h
#=> { :items=>{
:item=>[
{ :batters=>{
:batter=>[
{ :id=>"1001", :type=>"Regular" },
{ :id=>"1002", :type=>"Choc$olate" }
]
},
:id=>"0001",
42=>[
{ :id=>"5001", :type=>"None" },
{ "string"=>"5002", :type=>"Glazed" }
],
:type=>"donut"
}
]
}
}
如果保证(如示例中)唯一包含美元符号的符号或字符串是哈希键,则可以简化为以下内容。
require 'json'
JSON.parse(h.to_json.delete('$'))