2012-01-03 48 views
19

Estoy tratando de construir una gema envoltura API, y tener problemas para convertir las claves hash a un formato más Rubyish de la JSON que devuelve la API.Convirtiendo claves hash anidadas de CamelCase a snake_case en Ruby

El JSON contiene múltiples capas de anidamiento, tanto Hashes como Arrays. Lo que quiero hacer es convertir recursivamente todas las claves a snake_case para facilitar su uso.

Esto es lo que tengo hasta ahora:

def convert_hash_keys(value) 
    return value if (not value.is_a?(Array) and not value.is_a?(Hash)) 
    result = value.inject({}) do |new, (key, value)| 
    new[to_snake_case(key.to_s).to_sym] = convert_hash_keys(value) 
    new 
    end 
    result 
end 

Las llamadas por encima de este método para convertir cadenas a snake_case:

def to_snake_case(string) 
    string.gsub(/::/, '/'). 
    gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 
    gsub(/([a-z\d])([A-Z])/,'\1_\2'). 
    tr("-", "_"). 
    downcase 
end 

Idealmente, el resultado sería similar a la siguiente:

hash = {:HashKey => {:NestedHashKey => [{:Key => "value"}]}} 

convert_hash_keys(hash) 
# => {:hash_key => {:nested_hash_key => [{:key => "value"}]}} 

Estoy obteniendo la recursión incorrecta, y cada versión de este tipo de solución que he probado tampoco no convierte símbolos más allá del primer nivel, o va por la borda e intenta convertir todo el hash, incluidos los valores.

Tratando de resolver todo esto en una clase de ayuda, en lugar de modificar las funciones reales de Hash y String, si es posible.

Gracias de antemano.

+0

Antes de hacer cualquier otra cosa, 'si (no ... y no ...)' es un lugar perfecto para usar la ley de De Morgan. Deberías escribir 'a menos ... o ...'. – sawa

Respuesta

31

Debe tratar Array y Hash por separado. Y, si está en Rails, puede usar underscore en lugar de su homebrew to_snake_case. Primero un poco de ayuda para reducir el ruido:

def underscore_key(k) 
    k.to_s.underscore.to_sym 
    # Or, if you're not in Rails: 
    # to_snake_case(k.to_s).to_sym 
end 

Si sus valores hash tendrán llaves que no son símbolos o cadenas continuación, puede modificar underscore_key adecuadamente.

Si tiene una matriz, entonces solo quiere aplicar recursivamente convert_hash_keys a cada elemento de la matriz; si tiene un Hash, desea reparar las claves con underscore_key y aplicar convert_hash_keys a cada uno de los valores; Si usted tiene algo más a continuación, que desea pasar a través intacta:

def convert_hash_keys(value) 
    case value 
    when Array 
     value.map { |v| convert_hash_keys(v) } 
     # or `value.map(&method(:convert_hash_keys))` 
    when Hash 
     Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }] 
    else 
     value 
    end 
end 
+0

Funciona como un encanto. Gracias por su amabilidad. No estoy haciendo esto en Rails, pero creo que el código que utilicé para 'to_snake_case' proviene del método Rails' underscore'. –

+0

@Andrew: El 'string.gsub (/ :: /, '/')' en 'to_snake_case' me hace pensar que tienes razón sobre de dónde viene' to_snake_case'. Bienvenido a SO, disfruta de tu estancia. –

+0

No es necesario utilizar Rails, solo ActiveSupport, que nos permite seleccionar la rutina. 'require 'active_support/core_ext/string/inflections'' debería hacerlo:'' FooBar'.underscore => "foo_bar" '. –

17

Si utiliza rieles:

Ejemplo de almohadilla: camelCase a snake_case:

hash = { camelCase: 'value1', changeMe: 'value2' } 

hash.transform_keys { |key| key.to_s.underscore } 
# => { "camel_case" => "value1", "change_me" => "value2" } 

fuente: http://apidock.com/rails/v4.0.2/Hash/transform_keys

Para los atributos anidados usar deep_transform_keys en lugar de transform_keys, ejemplo:

hash = { camelCase: 'value1', changeMe: { hereToo: { andMe: 'thanks' } } } 

hash.deep_transform_keys { |key| key.to_s.underscore } 
# => {"camel_case"=>"value1", "change_me"=>{"here_too"=>{"and_me"=>"thanks"}}} 

fuente: http://apidock.com/rails/v4.2.7/Hash/deep_transform_keys

+0

esto solo transforma el primer nivel del hash, las claves hash anidadas aún permanecen como camel case. – nayiaw

+1

Gracias por la atención, actualicé mi respuesta. –

+0

¡Funciona como un amuleto, gracias! – born4new

0

Si está utilizando la biblioteca active_support, puede utilizar deep_transform_keys! como ese:

hash.deep_transform_keys! do |key| 
    k = key.to_s.snakecase rescue key 
    k.to_sym rescue key 
end