kgiszczak / shale

Shale is a Ruby object mapper and serializer for JSON, YAML, TOML, CSV and XML. It allows you to parse JSON, YAML, TOML, CSV and XML data and convert it into Ruby data structures, as well as serialize data structures into JSON, YAML, TOML, CSV or XML.
https://shalerb.org/
MIT License
618 stars 19 forks source link

Easier (un)nesting #9

Closed risen closed 1 year ago

risen commented 1 year ago

Hi,

I'm evaluating shale for a project, and was wondering if there's an easier way to get from flattened JSON to nested models.

Example:

class Person < ApplicationRecord
  belongs_to :address

  # attribute :id
  # attribute :name
end

class Address < ApplicationRecord
  # attribute :street
  # attribute :city
end

class PersonMapper < Shale::Mapper
  model Person

  attribute :id,     Shale::Type::Integer
  attribute :name,   Shale::Type::String
  attribute :street, Shale::Type::String
  attribute :city,   Shale::Type::String

  json do
    map :street, to: { address: :street }
    map :city,   to: { address: :city }
  end

  def address_from_json(model, key, value)
    model.build_address unless model.address
  end
end

… something like this. I could create a method for every single attribute that's "delegated" to address, but it gets verbose and tedious.

kgiszczak commented 1 year ago

Hey, currently there's no other way to do it - you have to use method for every attribute.

I wonder if something like this could solve most of this kind of use cases:

class PersonMapper < Shale::Mapper
  model Person

  attribute :id,     Shale::Type::Integer
  attribute :name,   Shale::Type::String

  json do
    map ['street', 'city'], using: { from: :address_from_json }
  end

  # hash would be { 'street' => 'foo', 'city' => 'bar' }
  def address_from_json(model, hash)
    model.build_address(hash)
  end
end

I'll have to think about all the use cases that can be solved by this. I don't want to add something that's coupled with a single, domain specific use case.

kgiszczak commented 1 year ago

I just released a new version that allows you to map group of elements using methods e.g.

class Person < Shale::Mapper
  attribute :name, Shale::Type::String

  json do
    group from: :name_from_json, to: :name_to_json do
      map 'first_name'
      map 'last_name'
    end
  end

  def name_from_json(model, value)
    model.name = "#{value['first_name']} #{value['last_name']}"
  end

  def name_to_json(model, doc)
    doc['first_name'] = model.name.split(' ')[0]
    doc['last_name'] = model.name.split(' ')[1]
  end
end

person = Person.from_json(<<~DATA)
{
  "first_name": "John",
  "last_name": "Doe"
}
DATA

Release notes are here: https://www.shalerb.org/releases.html#v080

I'm closing the issue, if you have more questions feel free to create a new one. Thanks.