Skip to content

Instantly share code, notes, and snippets.

@ericyd
Created October 11, 2019 17:06
Show Gist options
  • Save ericyd/dd380ae64ee0187104a6683ecea77683 to your computer and use it in GitHub Desktop.
Save ericyd/dd380ae64ee0187104a6683ecea77683 to your computer and use it in GitHub Desktop.
Ostructify: Recursively create OpenStructs of your hash or array or hashes
require 'ostruct'
# Recursively return OpenStructs from a hash or nested hash
# Handles array of hashes too
# Returns primitives when they are neither Hash nor Array
def ostructify(thing, camelize: false, snakify: false)
if thing.class == Hash
thing.reduce(OpenStruct.new) do |ostruct, (key, value)|
key = camelize ? camelize(key) : key
key = snakify ? snakify(key) : key
ostruct[key] = ostructify(value, camelize: camelize, snakify: snakify)
ostruct
end
elsif thing.class == Array
thing.map { |element| ostructify(element, camelize: camelize, snakify: snakify) }
else
thing
end
end
# less robust than the ActiveSupport version but fine for our purposes
# camelize(:my_value) # => "myValue"
# camelize('my_value') # => "myValue"
# camelize('my_value/path') # => "myValue/path"
# camelize('my_value::module') # => "myValue::module"
def camelize(stringable)
string = stringable.to_s
string = string.split('_')
string.first + string.drop(1).map(&:capitalize).join('')
end
# Modified from ActiveSupport:
# https://github.com/rails/rails/blob/3523b9d0096ec0229e9db336f0fb042b17c73978/activesupport/lib/active_support/inflector/methods.rb#L92-L101
# snakify(:myValue) # => "my_value"
# snakify('myValue') # => "my_value"
# snakify('myValue/path') # => "my_value/path"
# snakify('myValue::module') # => "my_value::module"
def snakify(stringable)
stringable.to_s
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.tr("-", "_")
.downcase
end
# --------------------------
# Tests
# --------------------------
# Arrays
initial = [1, 2, 3]
actual = ostructify(initial)
expected = [1, 2, 3]
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
initial = [[1], [2], [3]]
actual = ostructify(initial)
expected = [[1], [2], [3]]
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
initial = [[1], [2], [{h: 1, a: [{id: 2}]}]]
actual = ostructify(initial)
expected = [[1], [2], [OpenStruct.new(h: 1, a: [OpenStruct.new(id: 2)])]]
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# Hashes
# hash with simple values
initial = {h: 1, 'h2' => 2}
actual = ostructify(initial)
expected = OpenStruct.new(h: 1, 'h2' => 2)
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# hash with value that is array of hashes
initial = {h: 1, a: [ {t: 2}]}
actual = ostructify(initial)
expected = OpenStruct.new(h: 1, a: [ OpenStruct.new(t: 2)])
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# hash with value that is array of hashes with value that is array of strings
initial = {h: 1, a: [ {t: 2, aa: ['one']}]}
actual = ostructify(initial)
expected = OpenStruct.new(h: 1, a: [ OpenStruct.new(t: 2, aa: ['one'])])
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# hash with value that is array of hashes with value that is array of hashes
initial = {h: 1, a: [ {t: 2, aa: [{tt: 3}]}]}
actual = ostructify(initial)
expected = OpenStruct.new(h: 1, a: [ OpenStruct.new(t: 2, aa: [OpenStruct.new(tt: 3)])])
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# hash with some keys that are strings
initial = {'h': 1, 'a': [ {t: 2}]}
actual = ostructify(initial)
expected = OpenStruct.new('h': 1, 'a': [ OpenStruct.new(t: 2)])
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# integers
actual = ostructify(1)
expected = 1
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# chars
actual = ostructify('a')
expected = 'a'
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# classes
actual = ostructify(Object)
expected = Object
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# Camelize
initial = { my_key: 1, my_other_key: 2}
actual = ostructify(initial, camelize: true)
expected = OpenStruct.new(myKey: 1, myOtherKey: 2)
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
initial = { 'my_key' => 1, 'my_other_key' => 2}
actual = ostructify(initial, camelize: true)
expected = OpenStruct.new('myKey' => 1, 'myOtherKey' => 2)
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
initial = { 'my_key' => 1, my_other_key: [ { my_nested_key: 3 }]}
actual = ostructify(initial, camelize: true)
expected = OpenStruct.new('myKey' => 1, myOtherKey: [ OpenStruct.new(myNestedKey: 3)])
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
# Snakify
initial = { myKey: 1, myOtherKey: 2}
actual = ostructify(initial, snakify: true)
expected = OpenStruct.new(my_key: 1, my_other_key: 2)
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
initial = { 'myKey' => 1, 'myOtherKey' => 2}
actual = ostructify(initial, snakify: true)
expected = OpenStruct.new('my_key' => 1, 'my_other_key' => 2)
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
initial = { 'myKey' => 1, myOtherKey: [ { myNestedKey: 3 }]}
actual = ostructify(initial, snakify: true)
expected = OpenStruct.new('my_key' => 1, my_other_key: [ OpenStruct.new(my_nested_key: 3)])
if actual == expected
puts 'ok'
else
raise "not equal. Expected: #{expected.inspect}, Actual: #{actual.inspect}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment