Created
October 8, 2009 18:18
-
-
Save levinalex/205239 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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