Skip to content

Instantly share code, notes, and snippets.

@levinalex
Created October 8, 2009 18:18
Show Gist options
  • Save levinalex/205239 to your computer and use it in GitHub Desktop.
Save levinalex/205239 to your computer and use it in GitHub Desktop.
require 'json'
# takes a JSON object, removes specified subtrees and trims arrays
# returns a pretty printed string
#
# before = <<-EOF
# { "foo": {
# "one": { "a": 1, "b": {"c": 2} },
# "two": "value",
# "three": { "b": [1,2,3] }},
# "bar": [1,2,3,4,5] }
# EOF
#
# blacklist = [
# "foo:!two", # delete the value before['foo']['two']
# "foo:*:!b", # delete all the keys called "b" nested two levels under "foo"
# "bar:+" # only keep the first element of the foo-list
# ]
#
# after = JSONPrune.cut(before, :prune => blacklist)
#
# {
# "foo": {
# "two": ...,
# "three": {
# "b": [ ... ]
# },
# "one": {
# "a": 1,
# "b": { ... }
# }
# },
# "bar": [
# 1,
# ...
# ]
# }
#
class JSONPrune
# prune the given JSON-string or ruby Hash according to
# the rules given in opts[:prune]
#
def self.cut(obj, opts = {})
obj = JSON.parse(obj) if String === obj
(opts[:prune] || []).each do |ignore_str|
ignoring = ignore_str.split(":")
obj = cutr(obj, ignoring)
end
js = JSON.pretty_generate(obj)
js = js.gsub('"IGN_HSH"', "{ ... }")
js = js.gsub('"IGN_ARY"', "[ ... ]")
js = js.gsub('"IGN"', "...")
end
# recursively prune the current ruby object with the given rules
# replace pruned elements with marker strings
#
# cutr( [1,2,3], ["+"] ) #=> [ 1, "IGN" ]
# cutr( {"a"=>{"b"=>3}}, ["!a"] ) #=> { "a" => "IGN_HSH" }
# cutr( {"a"=>{"b"=>3}}, ["a", "!b"]) #=> { "a" => { "b" => "IGN" }}
#
def self.cutr(data, rules)
key, *rest = rules
if key =~ /^!(.*)/
name = $1
data[name] = case data[name]
when Hash then "IGN_HSH"
when Array then "IGN_ARY"
else "IGN"
end if data[name]
elsif key =~ /^\+$/
if Array === data
data = [data.first, "IGN"]
end
elsif key =~ /^\*$/
if Array === data
data = data.map { |elem| cutr(elem, rest) }
elsif Hash === data
data = data.inject({}) do |h,(k,v)| h[k] = cutr(v, rest); h end
end
else
name = key
if data[name] and not rest.empty?
data[name] = cutr(data[name], rest)
end
end
data
end
end
if __FILE__ == $0
require 'shoulda'
class FooTest < Test::Unit::TestCase
def assert_equal_ws(expected, actual)
expected = expected.gsub(/\s/, "")
actual = actual.gsub(/\s/,"")
assert_equal expected, actual
end
should "pass json through unchanged" do
assert_equal %Q({\n "foo": "bar"\n}),
JSONPrune.cut(%q({"foo": "bar"}))
end
should "ignore" do
assert_equal({"a" => 3, "b" => "IGN" },
JSONPrune.cutr({ "a" => 3, "b" => 4 } , ["!b"]))
assert_equal({"a" => 3, "b" => "IGN_HSH" },
JSONPrune.cutr({ "a" => 3, "b" => { "c" => 4 }} , ["!b"]))
assert_equal({"a" => 3, "b" => "IGN_ARY" },
JSONPrune.cutr({ "a" => 3, "b" => [ 4 ] } , ["!b"]))
end
should "distinguish between arrays and hashes" do
assert_equal %Q({\n "foo": { ... }\n}),
JSONPrune.cut(%q({"foo" : { "bar": "fred"}}), :prune => ["!foo"])
assert_equal %Q({\n "foo": [ ... ]\n}),
JSONPrune.cut(%q({"foo" : [ "bar", "fred"]}), :prune => ["!foo"])
assert_equal %Q({\n "foo": ...\n}),
JSONPrune.cut(%q({"foo" : 3}), :prune => ["!foo"])
end
should "work for nested attributes" do
assert_equal %Q({\n "foo": {\n "bar": { ... }\n }\n}),
JSONPrune.cut(%q({"foo" : { "bar": {"f":3}}}), :prune => ["foo:!bar"])
assert_equal %Q({\n "foo": {\n "bar": ...\n }\n}),
JSONPrune.cut(%q({"foo" : { "bar": 3}}), :prune => ["foo:!bar"])
end
should "not alter json if ignored thing does not exist" do
assert_equal %Q({\n "foo": { ... }\n}),
JSONPrune.cut(%q({"foo" : { "bar": "fred"}}),
:prune => ["!foo", "!doesnotexist", "foo:!doesnotexist"])
end
should "allow ignoring of subelements of arrays" do
assert_equal_ws %Q({"foo":[{"bar": { ... }},{"bar": ...}]}),
JSONPrune.cut(%q({"foo" : [{ "bar": {"baz":3}}, {"bar":3}]}), :prune => ["foo:*:!bar"])
end
should "allow ignoring of arrays" do
assert_equal %Q({\n "foo": [\n "bar",\n ...\n ]\n}),
JSONPrune.cut(%q({"foo": ["bar", "baz", "fred"]}),
:prune => ["foo:+"])
end
should "pass a complex testcase" do
before = <<-EOF
{ "foo": {
"one": { "a": 1, "b": {"c": 2} },
"two": "value",
"three": { "b": [1,2,3] }},
"bar": [1,2,3,4,5] }
EOF
blacklist = [
"foo:!two",
"foo:*:!b",
"bar:+"
]
expected = <<-EOF
{
"foo": {
"two": ...,
"three": {
"b": [ ... ]
},
"one": {
"a": 1,
"b": { ... }
}
},
"bar": [
1,
...
]
}
EOF
actual = JSONPrune.cut(before, :prune => blacklist)
assert_equal expected.chomp, actual
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment