ohler55 / oj

Optimized JSON
http://www.ohler.com/oj
MIT License
3.15k stars 252 forks source link

Is there a way to transform keys while dumping? #447

Closed rafbgarcia closed 7 years ago

rafbgarcia commented 7 years ago

What I'd like to do is to convert keys from snake to camel case, right now I'm using deep_transform_keys to achieve that, I'm curious if there is an alternative to that :)

Thanks

ohler55 commented 7 years ago

There is nothing built into Oj for that. You could use Scp parsers and convert the keys with the hash_key method or use the Saj parser and convert and keep track of the keys using it.

rafbgarcia commented 7 years ago

hey @ohler55, I've implemented a quick solution and found that it performs worse than deep_transform_keys and was wondering whether I did something wrong with that, would you mind taking a look?

class Handler < ::Oj::ScHandler
  def hash_start
    {}
  end

  def hash_set(h,k,v)
    h[k] = v
  end

  def array_start
    []
  end

  def hash_key(key)
    key.camelize(:lower)
  end

  def array_append(a,v)
    a << v
  end

  def add_value(v)
    p v
  end
end

Benchmark

I've used a real response from my server.

require 'benchmark'

json_string = '{"data":{"workingHours":[{"dayName":"Segunda-feira","formattedShifts":"08:00 às 12:00"},{"dayName":"Terça-feira","formattedShifts":"Fechado hoje"},{"dayName":"Quarta-feira","formattedShifts":"Fechado hoje"},{"dayName":"Quinta-feira","formattedShifts":"08:00 às 12:00 - 14:00 às 18:00"},{"dayName":"Sexta-feira","formattedShifts":"08:00 às 12:00"},{"dayName":"Sábado","formattedShifts":"08:00 às 12:00"},{"dayName":"Domingo","formattedShifts":"Fechado hoje"}],"paymentMethodSections":[{"name":"na entrega","paymentMethodGroups":[{"name":"Dinheiro","needsGroup":false,"paymentMethods":[{"id":"93","name":"Dinheiro"}]},{"name":"Cartão de crédito/débito","needsGroup":true,"paymentMethods":[{"id":"94","name":"Crédito - American Express"},{"id":"95","name":"Crédito - Diners"},{"id":"96","name":"Crédito - Elo"},{"id":"97","name":"Crédito - Hipercard"},{"id":"98","name":"Crédito - Mastercard"},{"id":"99","name":"Crédito - Visa"},{"id":"100","name":"Dédito - Elo"},{"id":"101","name":"Dédito - Mastercard"},{"id":"102","name":"Dédito - Visa"}]},{"name":"Vale refeição","needsGroup":true,"paymentMethods":[{"id":"103","name":"Alelo Refeição / Visa Vale"},{"id":"104","name":"Sodexo"},{"id":"105","name":"Ticket Restaurante"}]}]}],"categories":[{"id":"1","name":"Santa Maria","items":[{"id":"1","name":"Santa Maria 10L","description":"","originalPrice":7.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":7.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[{"id":"1","name":"Garrafão?","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":true,"complements":[{"id":"2","name":"Não","description":"","price":0.0,"quantity":0},{"id":"1","name":"Sim","description":"","price":1.0,"quantity":0}]},{"id":"129","name":"Raraw","required":false,"min":1,"max":3,"multipleChoice":true,"uniqueChoice":false,"complements":[{"id":"210","name":"Rerer","description":"","price":1.0,"quantity":0}]}]},{"id":"4","name":"Santa Maria 20L + Vasilhame","description":"d","originalPrice":20.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":20.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[{"id":"128","name":"Escolha os complementos","required":false,"min":1,"max":10,"multipleChoice":true,"uniqueChoice":false,"complements":[{"id":"207","name":"Leite em pó","description":"","price":2.5,"quantity":0},{"id":"208","name":"Chocolate granulado","description":"","price":1.0,"quantity":0},{"id":"209","name":"Granola","description":"","price":1.5,"quantity":0}]}]},{"id":"2","name":"Santa Maria 20L","description":"q","originalPrice":9.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":9.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[{"id":"130","name":"e","required":false,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":true,"complements":[{"id":"211","name":"ddd","description":"","price":2.22,"quantity":0}]}]},{"id":"3","name":"Santa Maria 10L + Vasilhame","description":"d","originalPrice":15.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":15.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[]}]},{"id":"7","name":"Pizzas Salgadas","items":[{"id":"37","name":"Pequena","description":null,"originalPrice":0.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":0.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[{"id":"61","name":"Escolha um sabor","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"100","name":"Portuguesa","description":null,"price":0.0,"quantity":0},{"id":"101","name":"Calabresa","description":null,"price":0.0,"quantity":0}]},{"id":"60","name":"Escolha a Massa","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"98","name":"Catupriy","description":null,"price":3.95,"quantity":0},{"id":"99","name":"Integral","description":null,"price":7.5,"quantity":0}]}]},{"id":"38","name":"Grande","description":null,"originalPrice":0.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":0.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[{"id":"62","name":"Escolha a Massa","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"102","name":"Catupriy","description":null,"price":3.95,"quantity":0},{"id":"103","name":"Integral","description":null,"price":7.5,"quantity":0}]},{"id":"63","name":"Escolha um sabor","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"104","name":"Portuguesa","description":null,"price":0.0,"quantity":0},{"id":"105","name":"Calabresa","description":null,"price":0.0,"quantity":0}]}]},{"id":"39","name":"Grande 2 sabores","description":null,"originalPrice":0.0,"hasDiscount":false,"priceWithDiscount":0.0,"currentPrice":0.0,"image":"http://localhost:5000/default_menu_item.svg","complementCategories":[{"id":"64","name":"Escolha a Massa","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"106","name":"Catupriy","description":null,"price":3.95,"quantity":0},{"id":"107","name":"Integral","description":null,"price":7.5,"quantity":0}]},{"id":"65","name":"Escolha um sabor","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"108","name":"1/2 Portuguesa","description":null,"price":0.0,"quantity":0},{"id":"109","name":"1/2 Calabresa","description":null,"price":0.0,"quantity":0}]},{"id":"66","name":"Escolha o segundo sabor","required":true,"min":null,"max":null,"multipleChoice":false,"uniqueChoice":false,"complements":[{"id":"110","name":"1/2 Portuguesa","description":null,"price":0.0,"quantity":0},{"id":"111","name":"1/2 Calabresa","description":null,"price":0.0,"quantity":0}]}]}]}],"totalReviewsCount":2,"reviewsWithComments":[{"average":5.0,"comments":"qwdqwdqwd","date":"1 dia","restaurantResponse":"","user":{"firstName":"Rafa","hasAvatar":false,"avatar":"http://localhost:5000/default_avatar.png"}}],"reviewsWithoutComments":[{"average":3.5,"comments":null,"date":"1 dia","restaurantResponse":"","user":{"firstName":"Rafa","hasAvatar":false,"avatar":"http://localhost:5000/default_avatar.png"}}]}}'
json = Oj.sc_parse(Handler.new, json_string)

t1 = Benchmark.measure { 1000.times { Oj.sc_parse(Handler.new, json_string) } }
t2 = Benchmark.measure { 1000.times { json.deep_transform_keys { |key| key.camelize(:lower) } } }

puts "Oj.sc_parse"
puts t1

puts "deep_transform_keys"
puts t2

results

Oj.sc_parse
  6.060000   1.610000   7.670000 (  6.875817)
deep_transform_keys
  4.930000   0.080000   5.010000 (  4.999562)

Thanks :)

ohler55 commented 7 years ago

It looks like you are comparing the parsing of a JSON string to running deep_transform_keys on the result the has already been camelized. Since camelids and deep_transform_keys are not part of Ruby core, are you also requiring rails or are you monkey patching Hash and String like rails does?

Anyway, a better comparison would be what you get as a result of using your handler against not using you handler and then running deep_transform_keys. Use what ever you like to generate the Hash in the case not using your handler.

rafbgarcia commented 7 years ago

That code is within a class in a Rails app/ folder.

Gotcha, thanks for the suggestion!

apuntovanini commented 6 years ago

@rafbgarcia how did you solve at the end?

rafbgarcia commented 6 years ago

I ended up using deep_transform_keys anyways, I don't have control over the JSON string in my code because I'm using ruby Graphql, hence I execute the graphql query and receive a Hash back, not a string.

From there I just do

hash.deep_transform_keys { |key| key.camelize(:lower) }

@apuntovanini