{"id":13411626,"url":"https://github.com/kgiszczak/shale","last_synced_at":"2025-05-14T07:08:14.141Z","repository":{"id":38265982,"uuid":"427610957","full_name":"kgiszczak/shale","owner":"kgiszczak","description":"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.","archived":false,"fork":false,"pushed_at":"2025-04-30T15:56:35.000Z","size":477,"stargazers_count":659,"open_issues_count":0,"forks_count":22,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-30T16:43:36.017Z","etag":null,"topics":["csv","csv-mapper","csv-serialization","json","json-mapping","json-serializer","object-mapper","ruby","serializer","toml","toml-mapping","toml-serializer","xml","xml-mapping","xml-serializer","yaml","yaml-mapping","yaml-serializer"],"latest_commit_sha":null,"homepage":"https://shalerb.org/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kgiszczak.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-11-13T08:36:37.000Z","updated_at":"2025-04-30T15:56:39.000Z","dependencies_parsed_at":"2024-10-23T19:25:04.855Z","dependency_job_id":"a6acdb19-dc14-4bba-815a-77983fa46cf9","html_url":"https://github.com/kgiszczak/shale","commit_stats":{"total_commits":161,"total_committers":10,"mean_commits":16.1,"dds":0.07453416149068326,"last_synced_commit":"e61ce80da70d7b61f15733e5cf0f5dcbd52de87b"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgiszczak%2Fshale","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgiszczak%2Fshale/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgiszczak%2Fshale/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgiszczak%2Fshale/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kgiszczak","download_url":"https://codeload.github.com/kgiszczak/shale/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092656,"owners_count":22013290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csv","csv-mapper","csv-serialization","json","json-mapping","json-serializer","object-mapper","ruby","serializer","toml","toml-mapping","toml-serializer","xml","xml-mapping","xml-serializer","yaml","yaml-mapping","yaml-serializer"],"created_at":"2024-07-30T20:01:15.138Z","updated_at":"2025-05-14T07:08:14.120Z","avatar_url":"https://github.com/kgiszczak.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Shale\n\nShale is a Ruby object mapper and serializer for JSON, YAML, TOML, CSV and XML.\nIt allows you to parse JSON, YAML, TOML, CSV and XML data and convert it into Ruby data structures,\nas well as serialize data structures into JSON, YAML, TOML, CSV or XML.\n\nDocumentation with interactive examples is available at [Shale website](https://www.shalerb.org)\n\n## Features\n\n* Convert JSON, YAML, TOML, CSV and XML to Ruby data model\n* Convert Ruby data model to JSON, YAML, TOML, CSV and XML\n* Generate JSON and XML Schema from Ruby models\n* Compile JSON and XML Schema into Ruby models\n* Out of the box support for JSON, YAML, Tomlib, toml-rb, CSV, Nokogiri, REXML and Ox parsers\n* Support for custom adapters\n\n## Installation\n\nShale supports Ruby (MRI) 3.0+\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'shale'\n```\n\nAnd then execute:\n\n```\n$ bundle install\n```\n\nOr install it yourself as:\n\n```\n$ gem install shale\n```\n\n## Contents\n\n* [Simple use case](#user-content-simple-use-case)\n* [Creating objects](#creating-objects)\n* [Converting JSON to object](#converting-json-to-object)\n* [Converting object to JSON](#converting-object-to-json)\n* [Converting YAML to object](#converting-yaml-to-object)\n* [Converting object to YAML](#converting-object-to-yaml)\n* [Converting TOML to object](#converting-toml-to-object)\n* [Converting object to TOML](#converting-object-to-toml)\n* [Converting Hash to object](#converting-hash-to-object)\n* [Converting object to Hash](#converting-object-to-hash)\n* [Converting XML to object](#converting-xml-to-object)\n* [Converting object to XML](#converting-object-to-xml)\n* [Converting CSV to object](#converting-csv-to-object)\n* [Converting object to CSV](#converting-object-to-csv)\n* [Converting collections](#converting-collections)\n* [Mapping JSON keys to object attributes](#mapping-json-keys-to-object-attributes)\n* [Mapping YAML keys to object attributes](#mapping-yaml-keys-to-object-attributes)\n* [Mapping TOML keys to object attributes](#mapping-toml-keys-to-object-attributes)\n* [Mapping CSV columns to object attributes](#mapping-csv-columns-to-object-attributes)\n* [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)\n* [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)\n* [Using XML namespaces](#using-xml-namespaces)\n* [Rendering nil values](#rendering-nil-values)\n* [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)\n* [Delegating fields to child attributes](#delegating-fields-to-child-attributes)\n* [Additional options](#additional-options)\n* [Overriding attribute methods](#overriding-attribute-methods)\n* [Using custom models](#using-custom-models)\n* [Supported types](#supported-types)\n* [Writing your own type](#writing-your-own-type)\n* [Adapters](#adapters)\n* [Generating JSON Schema](#generating-json-schema)\n* [Compiling JSON Schema into Shale model](#compiling-json-schema-into-shale-model)\n* [Generating XML Schema](#generating-xml-schema)\n* [Compiling XML Schema into Shale model](#compiling-xml-schema-into-shale-model)\n\n## Usage\n\n### Simple use case\n\n```ruby\nrequire 'shale'\n\nclass Address \u003c Shale::Mapper\n  attribute :city, :string\n  attribute :street, :string\n  attribute :zip, :string\nend\n\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :age, :integer\n  attribute :married, :boolean, default: -\u003e { false }\n  attribute :hobbies, :string, collection: true\n  attribute :address, Address\nend\n```\n\n- `default: -\u003e { 'value' }` - add a default value to attribute (it must be a proc that returns value)\n- `collection: true` - indicates that a attribute is a collection\n\n### Creating objects\n\n```ruby\nperson = Person.new(\n  first_name: 'John',\n  last_name: 'Doe',\n  age: 50,\n  hobbies: ['Singing', 'Dancing'],\n  address: Address.new(city: 'London', street: 'Oxford Street', zip: 'E1 6AN'),\n)\n```\n\n### Converting JSON to object\n\n```ruby\nperson = Person.from_json(\u003c\u003c~DATA)\n{\n  \"first_name\": \"John\",\n  \"last_name\": \"Doe\",\n  \"age\": 50,\n  \"married\": false,\n  \"hobbies\": [\"Singing\", \"Dancing\"],\n  \"address\": {\n    \"city\": \"London\",\n    \"street\": \"Oxford Street\",\n    \"zip\": \"E1 6AN\"\n  }\n}\nDATA\n\n# =\u003e\n#\n# #\u003cPerson:0x00007f9bc3086d60\n#  @address=\n#   #\u003cAddress:0x00007f9bc3086748\n#    @city=\"London\",\n#    @street=\"Oxford Street\",\n#    @zip=\"E1 6AN\"\u003e,\n#  @age=50,\n#  @first_name=\"John\",\n#  @hobbies=[\"Singing\", \"Dancing\"],\n#  @last_name=\"Doe\",\n#  @married=false\u003e\n```\n\n### Converting object to JSON\n\n```ruby\nperson.to_json\n\n# =\u003e\n#\n# {\n#   \"first_name\": \"John\",\n#   \"last_name\": \"Doe\",\n#   \"age\": 50,\n#   \"married\": false,\n#   \"hobbies\": [\"Singing\", \"Dancing\"],\n#   \"address\": {\n#     \"city\": \"London\",\n#     \"street\": \"Oxford Street\",\n#     \"zip\": \"E1 6AN\"\n#   }\n# }\n```\n\n### Converting YAML to object\n\n```ruby\nperson = Person.from_yaml(\u003c\u003c~DATA)\nfirst_name: John\nlast_name: Doe\nage: 50\nmarried: false\nhobbies:\n- Singing\n- Dancing\naddress:\n  city: London\n  street: Oxford Street\n  zip: E1 6AN\nDATA\n```\n\n### Converting object to YAML\n\n```ruby\nperson.to_yaml\n\n# =\u003e\n#\n# ---\n# first_name: John\n# last_name: Doe\n# age: 50\n# married: false\n# hobbies:\n# - Singing\n# - Dancing\n# address:\n#   city: London\n#   street: Oxford Street\n#   zip: E1 6AN\n```\n\n### Converting TOML to object\n\nTo use TOML with Shale you have to set adapter you want to use.\nIt comes with adapters for [Tomlib](https://github.com/kgiszczak/tomlib) and\n[toml-rb](https://github.com/emancu/toml-rb).\nFor details see [Adapters](#adapters) section.\n\nTo set it, first make sure Tomlib gem is installed:\n\n```\n$ gem install tomlib\n```\n\nthen setup adapter:\n\n```ruby\nrequire 'sahle/adapter/tomlib'\nShale.toml_adapter = Shale::Adapter::Tomlib\n\n# Alternatively if you'd like to use toml-rb, use:\nrequire 'shale/adapter/toml_rb'\nShale.toml_adapter = Shale::Adapter::TomlRB\n```\n\nNow you can use TOML with Shale:\n\n```ruby\nperson = Person.from_toml(\u003c\u003c~DATA)\nfirst_name = \"John\"\nlast_name = \"Doe\"\nage = 50\nmarried = false\nhobbies = [\"Singing\", \"Dancing\"]\n[address]\ncity = \"London\"\nstreet = \"Oxford Street\"\nzip = \"E1 6AN\"\nDATA\n```\n\n### Converting object to TOML\n\n```ruby\nperson.to_toml\n\n# =\u003e\n#\n# first_name = \"John\"\n# last_name = \"Doe\"\n# age = 50\n# married = false\n# hobbies = [ \"Singing\", \"Dancing\" ]\n#\n# [address]\n# city = \"London\"\n# street = \"Oxford Street\"\n# zip = \"E1 6AN\"\n```\n\n### Converting Hash to object\n\n```ruby\nperson = Person.from_hash(\n  'first_name' =\u003e 'John',\n  'last_name' =\u003e 'Doe',\n  'age' =\u003e 50,\n  'married' =\u003e false,\n  'hobbies' =\u003e ['Singing', 'Dancing'],\n  'address' =\u003e {\n    'city'=\u003e'London',\n    'street'=\u003e'Oxford Street',\n    'zip'=\u003e'E1 6AN'\n  },\n)\n```\n\n### Converting object to Hash\n\n```ruby\nperson.to_hash\n\n# =\u003e\n#\n# {\n#   \"first_name\"=\u003e\"John\",\n#   \"last_name\"=\u003e\"Doe\",\n#   \"age\"=\u003e50,\n#   \"married\"=\u003efalse,\n#   \"hobbies\"=\u003e[\"Singing\", \"Dancing\"],\n#   \"address\"=\u003e{\"city\"=\u003e\"London\", \"street\"=\u003e\"Oxford Street\", \"zip\"=\u003e\"E1 6AN\"}\n# }\n ```\n\n### Converting XML to object\n\nTo use XML with Shale you have to set adapter you want to use.\nShale comes with adapters for REXML, Nokogiri and OX parsers.\nFor details see [Adapters](#adapters) section.\n\n```ruby\nrequire 'shale/adapter/rexml'\nShale.xml_adapter = Shale::Adapter::REXML\n```\n\nNow you can use XML with Shale:\n\n```ruby\nperson = Person.from_xml(\u003c\u003c~DATA)\n\u003cperson\u003e\n  \u003cfirst_name\u003eJohn\u003c/first_name\u003e\n  \u003clast_name\u003eDoe\u003c/last_name\u003e\n  \u003cage\u003e50\u003c/age\u003e\n  \u003cmarried\u003efalse\u003c/married\u003e\n  \u003chobbies\u003eSinging\u003c/hobbies\u003e\n  \u003chobbies\u003eDancing\u003c/hobbies\u003e\n  \u003caddress\u003e\n    \u003ccity\u003eLondon\u003c/city\u003e\n    \u003cstreet\u003eOxford Street\u003c/street\u003e\n    \u003czip\u003eE1 6AN\u003c/zip\u003e\n  \u003c/address\u003e\n\u003c/person\u003e\nDATA\n```\n\n### Converting object to XML\n\n```ruby\nperson.to_xml\n\n# =\u003e\n#\n# \u003cperson\u003e\n#   \u003cfirst_name\u003eJohn\u003c/first_name\u003e\n#   \u003clast_name\u003eDoe\u003c/last_name\u003e\n#   \u003cage\u003e50\u003c/age\u003e\n#   \u003cmarried\u003efalse\u003c/married\u003e\n#   \u003chobbies\u003eSinging\u003c/hobbies\u003e\n#   \u003chobbies\u003eDancing\u003c/hobbies\u003e\n#   \u003caddress\u003e\n#     \u003ccity\u003eLondon\u003c/city\u003e\n#     \u003cstreet\u003eOxford Street\u003c/street\u003e\n#     \u003czip\u003eE1 6AN\u003c/zip\u003e\n#   \u003c/address\u003e\n# \u003c/person\u003e\n```\n\n### Converting CSV to object\n\nTo use CSV with Shale you have to set adapter.\nShale comes with adapter for [csv](https://github.com/ruby/csv).\nFor details see [Adapters](#adapters) section.\n\nTo set it, first make sure CSV gem is installed:\n\n```\n$ gem install csv\n```\n\nthen setup adapter:\n\n```ruby\nrequire 'shale/adapter/csv'\nShale.csv_adapter = Shale::Adapter::CSV\n```\n\nNow you can use CSV with Shale.\n\nCSV represents a flat data structure, so you can't map properties to complex types directly,\nbut you can use methods to map properties to complex types\n(see [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)\nsection).\n\n`.from_csv` method allways returns an array of records.\n\n```ruby\npeople = Person.from_csv(\u003c\u003c~DATA)\nJohn,Doe,50,false\nDATA\n```\n\n### Converting object to CSV\n\n```ruby\npeople[0].to_csv # or Person.to_csv(people) if you want to convert a collection\n\n# =\u003e\n#\n# John,Doe,50,false\n```\n\n### Converting collections\n\nShale allows converting collections for formats that support it (JSON, YAML and CSV).\nTo convert Ruby array to JSON:\n\n```ruby\nperson1 = Person.new(name: 'John Doe')\nperson2 = Person.new(name: 'Joe Sixpack')\n\nPerson.to_json([person1, person2], pretty: true)\n# or Person.to_yaml([person1, person2])\n# or Person.to_csv([person1, person2])\n\n# =\u003e\n#\n# [\n#   { \"name\": \"John Doe\" },\n#   { \"name\": \"Joe Sixpack\" }\n# ]\n```\n\nTo convert JSON array to Ruby:\n\n```ruby\nPerson.from_json(\u003c\u003c~JSON)\n[\n  { \"name\": \"John Doe\" },\n  { \"name\": \"Joe Sixpack\" }\n]\nJSON\n\n# =\u003e\n#\n# [\n#   #\u003cPerson:0x00000001033dbce8 @name=\"John Doe\"\u003e,\n#   #\u003cPerson:0x00000001033db4c8 @name=\"Joe Sixpack\"\u003e\n# ]\n```\n\n### Mapping JSON keys to object attributes\n\nBy default keys are named the same as attributes. To use custom keys use:\n\n:warning: **Declaring custom mapping removes default mapping for given format!**\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n\n  json do\n    map 'firstName', to: :first_name\n    map 'lastName', to: :last_name\n  end\nend\n```\n\n### Mapping YAML keys to object attributes\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n\n  yaml do\n    map 'firstName', to: :first_name\n    map 'lastName', to: :last_name\n  end\nend\n```\n\n### Mapping TOML keys to object attributes\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n\n  toml do\n    map 'firstName', to: :first_name\n    map 'lastName', to: :last_name\n  end\nend\n```\n\n### Mapping CSV columns to object attributes\n\nFor CSV the order of mapping matters, the first argument in the `map` method is only\nused as a label in header row. So, in the example below the first column will be mapped\nto `:first_name` attribute and the second column to `:last_name`.\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n\n  csv do\n    map 'firstName', to: :first_name\n    map 'lastName', to: :last_name\n  end\nend\n```\n\n### Mapping Hash keys to object attributes\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n\n  hsh do\n    map 'firstName', to: :first_name\n    map 'lastName', to: :last_name\n  end\nend\n```\n\n### Mapping XML elements and attributes to object attributes\n\nXML is more complicated format than JSON or YAML. To map elements, attributes and content use:\n\n```ruby\nclass Address \u003c Shale::Mapper\n  attribute :street, :string\n  attribute :city, :string\n  attribute :zip, :string\n\n  xml do\n    map_content to: :street\n    map_element 'City', to: :city\n    map_element 'ZIP', to: :zip\n  end\nend\n\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :age, :integer\n  attribute :hobbies, :string, collection: true\n  attribute :address, Address\n\n  xml do\n    root 'Person'\n\n    map_attribute 'age', to: :age\n\n    map_element 'FirstName', to: :first_name\n    map_element 'LastName', to: :last_name\n    map_element 'Hobby', to: :hobbies\n    map_element 'Address', to: :address\n  end\nend\n\nperson = Person.from_xml(\u003c\u003c~DATA)\n\u003cPerson age=\"50\"\u003e\n  \u003cFirstName\u003eJohn\u003c/FirstName\u003e\n  \u003cLastName\u003eDoe\u003c/LastName\u003e\n  \u003cHobby\u003eSinging\u003c/Hobby\u003e\n  \u003cHobby\u003eDancing\u003c/Hobby\u003e\n  \u003cAddress\u003e\n    Oxford Street\n    \u003cCity\u003eLondon\u003c/City\u003e\n    \u003cZIP\u003eE1 6AN\u003c/ZIP\u003e\n  \u003c/Address\u003e\n\u003c/person\u003e\nDATA\n```\n\n- `root` - name of the root element\n- `map_element` - map content of element to attribute\n- `map_attribute` - map element's attribute to attribute\n- `map_content` - map first text node to attribute\n\nYou can use `cdata: true` option on `map_element` and `map_content` to handle CDATA nodes:\n\n```ruby\nclass Address \u003c Shale::Mapper\n  attribute :content, :string\n\n  xml do\n    map_content to: :content, cdata: true\n  end\nend\n\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :address, Address\n\n  xml do\n    root 'Person'\n\n    map_element 'FirstName', to: :first_name, cdata: true\n    map_element 'Address', to: :address\n  end\nend\n\nperson = Person.from_xml(\u003c\u003c~DATA)\n\u003cPerson\u003e\n  \u003cFirstName\u003e\u003c![CDATA[John]]\u003e\u003c/FirstName\u003e\n  \u003cAddress\u003e\u003c![CDATA[Oxford Street]]\u003e\u003c/Address\u003e\n\u003c/person\u003e\nDATA\n```\n\n### Using XML namespaces\n\nTo map namespaced elements and attributes use `namespace` and `prefix` properties on\n`map_element` and `map_attribute`\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :age, :integer\n\n  xml do\n    root 'person'\n\n    map_element 'first_name', to: :first_name, namespace: 'http://ns1.com', prefix: 'ns1'\n    map_element 'last_name', to: :last_name, namespace: 'http://ns2.com', prefix: 'ns2'\n    map_attribute 'age', to: :age, namespace: 'http://ns2.com', prefix: 'ns2'\n  end\nend\n\nperson = Person.from_xml(\u003c\u003c~DATA)\n\u003cperson xmlns:ns1=\"http://ns1.com\" xmlns:ns2=\"http://ns2.com\" ns2:age=\"50\"\u003e\n  \u003cns1:first_name\u003eJohn\u003c/ns1:first_name\u003e\n  \u003cns2:last_name\u003eDoe\u003c/ns2:last_name\u003e\n\u003c/person\u003e\nDATA\n```\n\nTo define default namespace for all elements use `namespace` declaration\n(this will define namespace on elements only, if you want to define namespace on an attribute\nexplicitly declare it on `map_attribute`).\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :middle_name, :string\n  attribute :last_name, :string\n  attribute :age, :integer\n  attribute :hobby, :string\n\n  xml do\n    root 'person'\n    namespace 'http://ns1.com', 'ns1'\n\n    map_element 'first_name', to: :first_name\n\n    # undeclare namespace on 'middle_name' element\n    map_element 'middle_name', to: :middle_name, namespace: nil, prefix: nil\n\n    # overwrite default namespace\n    map_element 'last_name', to: :last_name, namespace: 'http://ns2.com', prefix: 'ns2'\n\n    map_attribute 'age', to: :age\n    map_attribute 'hobby', to: :hobby, namespace: 'http://ns1.com', prefix: 'ns1'\n  end\nend\n\nperson = Person.from_xml(\u003c\u003c~DATA)\n\u003cns1:person xmlns:ns1=\"http://ns1.com\" xmlns:ns2=\"http://ns2.com\" age=\"50\" ns1:hobby=\"running\"\u003e\n  \u003cns1:first_name\u003eJohn\u003c/ns1:first_name\u003e\n  \u003cmiddle_name\u003eJoe\u003c/middle_name\u003e\n  \u003cns2:last_name\u003eDoe\u003c/ns2:last_name\u003e\n\u003c/ns1:person\u003e\nDATA\n```\n\n### Rendering nil values\n\nFor JSON, YAML, TOML and XML by default, elements with `nil` value are not rendered.\nYou can change this behavior by using `render_nil: true` on a mapping.\nFor CSV the default is to render `nil` elements.\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :age, :integer\n\n  json do\n    map 'first_name', to: :first_name, render_nil: true\n    map 'last_name', to: :last_name, render_nil: false\n    map 'age', to: :age, render_nil: true\n  end\n\n  xml do\n    root 'person'\n\n    map_element 'first_name', to: :first_name, render_nil: true\n    map_element 'last_name', to: :last_name, render_nil: false\n    map_attribute 'age', to: :age, render_nil: true\n  end\nend\n\nperson = Person.new(first_name: nil, last_name: nil, age: nil)\n\nputs person.to_json(pretty: true)\n\n# =\u003e\n#\n# {\n#   \"first_name\": null,\n#   \"age\": \"null\"\n# }\n\nputs person.to_xml(pretty: true)\n\n# =\u003e\n#\n# \u003cperson age=\"\"\u003e\n#   \u003cfirst_name/\u003e\n# \u003c/person\u003e\n```\n\nIf you want to change how nil values are rendered for all mappings you can use `render_nil` method:\n\n```ruby\nclass Base \u003c Shale::Mapper\n  json do\n    # change render_nil default for all JSON mappings inheriting from Base class\n    render_nil true\n  end\nend\n\nclass Person \u003c Base\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :age, :integer\n\n  json do\n    # override default from Base class\n    render_nil false\n\n    map 'first_name', to: :first_name\n    map 'last_name', to: :last_name\n    map 'age', to: :age, render_nil: true # override default\n  end\nend\n```\n\n:warning: The default affects only the mappings declared after setting the default value e.g.\n\n```ruby\nclass Person \u003c Base\n  attribute :first_name, :string\n  attribute :last_name, :string\n\n  json do\n    render_nil false\n    map 'first_name', to: :first_name # render_nil will be false for this mapping\n\n    render_nil true\n    map 'last_name', to: :last_name # render_nil will be true for this mapping\n  end\nend\n```\n\n### Using methods to extract and generate data\n\nIf you need full controll over extracting and generating data from/to document,\nyou can use methods to do so:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :hobbies, :string, collection: true\n  attribute :street, :string\n  attribute :city, :string\n\n  json do\n    map 'hobbies', using: { from: :hobbies_from_json, to: :hobbies_to_json }\n    map 'address', using: { from: :address_from_json, to: :address_to_json }\n  end\n\n  xml do\n    root 'Person'\n\n    map_attribute 'hobbies', using: { from: :hobbies_from_xml, to: :hobbies_to_xml }\n    map_element 'Address', using: { from: :address_from_xml, to: :address_to_xml }\n  end\n\n  def hobbies_from_json(model, value)\n    model.hobbies = value.split(',').map(\u0026:strip)\n  end\n\n  def hobbies_to_json(model, doc)\n    doc['hobbies'] = model.hobbies.join(', ')\n  end\n\n  def address_from_json(model, value)\n    model.street = value['street']\n    model.city = value['city']\n  end\n\n  def address_to_json(model, doc)\n    doc['address'] = { 'street' =\u003e model.street, 'city' =\u003e model.city }\n  end\n\n  def hobbies_from_xml(model, value)\n    model.hobbies = value.split(',').map(\u0026:strip)\n  end\n\n  def hobbies_to_xml(model, element, doc)\n    doc.add_attribute(element, 'hobbies', model.hobbies.join(', '))\n  end\n\n  def address_from_xml(model, node)\n    model.street = node.children.find { |e| e.name == 'Street' }.text\n    model.city = node.children.find { |e| e.name == 'City' }.text\n  end\n\n  def address_to_xml(model, parent, doc)\n    street_element = doc.create_element('Street')\n    doc.add_text(street_element, model.street.to_s)\n\n    city_element = doc.create_element('City')\n    doc.add_text(city_element, model.city.to_s)\n\n    address_element = doc.create_element('Address')\n    doc.add_element(address_element, street_element)\n    doc.add_element(address_element, city_element)\n    doc.add_element(parent, address_element)\n  end\nend\n\nperson = Person.from_json(\u003c\u003c~DATA)\n{\n  \"hobbies\": \"Singing, Dancing, Running\",\n  \"address\": {\n    \"street\": \"Oxford Street\",\n    \"city\": \"London\"\n  }\n}\nDATA\n\nperson = Person.from_xml(\u003c\u003c~DATA)\n\u003cPerson hobbies=\"Singing, Dancing, Running\"\u003e\n  \u003cAddress\u003e\n    \u003cStreet\u003eOxford Street\u003c/Street\u003e\n    \u003cCity\u003eLondon\u003c/City\u003e\n  \u003c/Address\u003e\n\u003c/Person\u003e\nDATA\n\n# =\u003e\n#\n# #\u003cPerson:0x00007f9bc3086d60\n#  @hobbies=[\"Singing\", \"Dancing\", \"Running\"],\n#  @street=\"Oxford Street\",\n#  @city=\"London\"\u003e\n```\n\nYou can also pass a `context` object that will be available in extractor/generator methods:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :password, :string\n\n  json do\n    map 'password', using: { from: :password_from_json, to: :password_to_json }\n  end\n\n  def password_from_json(model, value, context)\n    if context.admin?\n      model.password = value\n    else\n      model.password = '*****'\n    end\n  end\n\n  def password_to_json(model, doc, context)\n    if context.admin?\n      doc['password'] = model.password\n    else\n      doc['password'] = '*****'\n    end\n  end\nend\n\nPerson.new(password: 'secret').to_json(context: current_user)\n```\n\nIf you want to work on multiple elements at a time you can group them using `group` block:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :name, :string\n\n  json do\n    group from: :name_from_json, to: :name_to_json do\n      map 'first_name'\n      map 'last_name'\n    end\n  end\n\n  xml do\n    group from: :name_from_xml, to: :name_to_xml do\n      map_content\n      map_element 'first_name'\n      map_attribute 'last_name'\n    end\n  end\n\n  def name_from_json(model, value)\n    model.name = \"#{value['first_name']} #{value['last_name']}\"\n  end\n\n  def name_to_json(model, doc)\n    doc['first_name'] = model.name.split(' ')[0]\n    doc['last_name'] = model.name.split(' ')[1]\n  end\n\n  def name_from_xml(model, value)\n    # value =\u003e { content: ..., attributes: {}, elements: {} }\n  end\n\n  def name_to_xml(model, element, doc)\n    # ...\n  end\nend\n\nPerson.from_json(\u003c\u003c~DATA)\n{\n  \"first_name\": \"John\",\n  \"last_name\": \"Doe\"\n}\nDATA\n\n# =\u003e #\u003cPerson:0x00007f9bc3086d60 @name=\"John Doe\"\u003e\n```\n\n### Delegating fields to child attributes\n\nTo delegate fields to child complex types you can use `receiver: :child` declaration:\n\n```ruby\nclass Address \u003c Shale::Mapper\n  attribute :city, :string\n  attribute :street, :string\nend\n\nclass Person \u003c Shale::Mapper\n  attribute :name, :string\n  attribute :address, Address\n\n  json do\n    map 'name', to: :name\n    map 'city', to: :city, receiver: :address\n    map 'street', to: :street, receiver: :address\n  end\nend\n\nperson = Person.from_json(\u003c\u003c~DATA)\n{\n  \"name\": \"John Doe\",\n  \"city\": \"London\",\n  \"street\": \"Oxford Street\"\n}\nDATA\n\n# =\u003e\n#\n# #\u003cPerson:0x00007f9bc3086d60\n#  @name=\"John Doe\",\n#  @address=#\u003cAddress:0x0000000102cbd218 @city=\"London\", @street=\"Oxford Street\"\u003e\u003e\n```\n\n### Additional options\n\nYou can control which attributes to render and parse by\nusing `only: []` and `except: []` parameters.\n\n```ruby\n# e.g. if you have this model graph:\nperson = Person.new(\n  first_name: 'John'\n  last_name: 'Doe',\n  address: Address.new(city: 'London', street: 'Oxford Street')\n)\n\n# if you want to render only `first_name` and `address.city` do:\nperson.to_json(only: [:first_name, address: [:city]], pretty: true)\n\n# =\u003e\n#\n# {\n#   \"first_name\": \"John\",\n#   \"address\": {\n#     \"city\": \"London\"\n#   }\n# }\n\n# and if you don't need an address you can do:\nperson.to_json(except: [:address], pretty: true)\n\n# =\u003e\n#\n# {\n#   \"first_name\": \"John\",\n#   \"last_name\": \"Doe\"\n# }\n```\n\nIt works the same for parsing:\n\n```ruby\n# e.g. if you want to parse only `address.city` do:\nPerson.from_json(doc, only: [address: [:city]])\n\n# =\u003e\n#\n# #\u003cPerson:0x0000000113d7a488\n#  @first_name=nil,\n#  @last_name=nil,\n#  @address=#\u003cAddress:0x0000000113d7a140 @street=nil, @city=\"London\"\u003e\u003e\n\n# and if you don't need an `address`:\nPerson.from_json(doc, except: [:address])\n\n# =\u003e\n#\n# #\u003cPerson:0x0000000113d7a488\n#  @first_name=\"John\",\n#  @last_name=\"Doe\",\n#  @address=nil\u003e\n```\n\nIf you need formatted output you can pass `pretty: true` parameter to `#to_json` and `#to_xml`\n\n```ruby\nperson.to_json(pretty: true)\n\n# =\u003e\n#\n# {\n#   \"name\": \"John Doe\",\n#   \"address\": {\n#     \"city\": \"London\"\n#   }\n# }\n```\n\nYou can also add an XML declaration by passing `declaration: true` and `encoding: true`\nor if you want to spcify version: `declaration: '1.1'` and `encoding: 'ASCII'` to `#to_xml`\n\n```ruby\nperson.to_xml(pretty: true, declaration: true, encoding: true)\n\n# =\u003e\n#\n# \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n# \u003cPerson\u003e\n#   \u003cAddress city=\"London\"/\u003e\n# \u003c/Person\u003e\n```\n\nFor CSV you can pass `headers: true` to indicate that the first row contains column\nnames and shouldn't be included in the returned collection. It also accepts all the options that\n[CSV parser](https://ruby-doc.org/stdlib-3.1.2/libdoc/csv/rdoc/CSV.html#class-CSV-label-Options) accepts.\n\n```ruby\nclass Person\n  attribute :first_name, :string\n  attribute :last_name, :string\nend\n\npeople = Person.from_csv(\u003c\u003c~DATA, headers: true, col_sep: '|')\n  first_name|last_name\n  John|Doe\n  James|Sixpack\nDATA\n\n# =\u003e\n#\n# [\n#   #\u003cPerson:0x0000000113d7a488 @first_name=\"John\", @last_name=\"Doe\"\u003e,\n#   #\u003cPerson:0x0000000113d7a488 @first_name=\"James\", @last_name=\"Sixpack\"\u003e\n# ]\n\nPerson.to_csv(people, headers: true, col_sep: '|')\n\n# =\u003e\n#\n# first_name|last_name\n# John|Doe\n# James|Sixpack\n```\n\nMost adapters accept options specific to them. Eg. if you want to be able to work\nwith NaN values in JSON:\n\n```ruby\nclass Person\n  attribute :age, :float\nend\n\nperson = Person.from_json('{\"age\": NaN}', allow_nan: true)\n\n# =\u003e\n#\n# #\u003cPerson:0x0000000113d7a488 @age=Float::NAN\u003e\n\nPerson.to_json(person, allow_nan: true)\n\n# =\u003e\n#\n# {\n#   \"age\": NaN\n# }\n```\n\n### Overriding attribute methods\n\nIt's possible to override an attribute method to change its output:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :gender, :string\n\n  def gender\n    if super == 'm'\n      'male'\n    else\n      'female'\n    end\n  end\nend\n\nputs Person.from_json('{\"gender\":\"m\"}')\n\n# =\u003e\n#\n# #\u003cPerson:0x00007f9bc3086d60\n#  @gender=\"male\"\u003e\n```\n\nBe conscious that the original attribute value will be lost after its transformation though:\n\n```ruby\nputs User.from_json('{\"gender\":\"m\"}').to_json\n# =\u003e {\"gender\":\"male\"}\n```\n\nIt'll no longer return gender `m`.\n\n### Using custom models\n\nBy default Shale combines mapper and model into one class. If you want to use your own classes\nas models you can do it by using `model` directive on the mapper:\n\n```ruby\nclass Address\n  attr_accessor :street, :city\nend\n\nclass Person\n  attr_accessor :first_name, :last_name, :address\nend\n\nclass AddressMapper \u003c Shale::Mapper\n  model Address\n\n  attribute :street, :string\n  attribute :city, :string\nend\n\nclass PersonMapper \u003c Shale::Mapper\n  model Person\n\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :address, AddressMapper\nend\n\nperson = PersonMapper.from_json(\u003c\u003c~DATA)\n{\n  \"first_name\": \"John\",\n  \"last_name\": \"Doe\",\n  \"address\": {\n    \"street\": \"Oxford Street\",\n    \"city\": \"London\"\n  }\n}\nDATA\n\n# =\u003e\n#\n# #\u003cPerson:0x0000000113d7a488\n#  @first_name=\"John\",\n#  @last_name=\"Doe\",\n#  @address=#\u003cAddress:0x0000000113d7a140 @street=\"Oxford Street\", @city=\"London\"\u003e\u003e\n\nPersonMapper.to_json(person, pretty: true)\n\n# =\u003e\n#\n# {\n#   \"first_name\": \"John\",\n#   \"last_name\": \"Doe\",\n#   \"address\": {\n#     \"street\": \"Oxford Street\",\n#     \"city\": \"London\"\n#   }\n# }\n```\n\n### Supported types\n\nShale supports these types out of the box:\n\n- `:boolean` (`Shale::Type::Boolean`)\n- `:date` (`Shale::Type::Date`)\n- `:float` (`Shale::Type::Float`)\n- `:decimal` (`Shale::Type::Decimal`)\n- `:integer` (`Shale::Type::Integer`)\n- `:string` (`Shale::Type::String`)\n- `:time` (`Shale::Type::Time`)\n\nThe symbol type alias and the type class are interchangeable:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :age, Shale::Type::Integer\n  # attribute :age, :integer\nend\n```\n\n### Writing your own type\n\nTo add your own type extend it from `Shale::Type::Value` and implement `.cast` class method.\n\n```ruby\nrequire 'shale/type/value'\n\nclass MyIntegerType \u003c Shale::Type::Value\n  def self.cast(value)\n    value.to_i\n  end\nend\n```\n\nThen you can use it in your model:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :age, MyIntegerType\nend\n```\n\nYou can also register your own type with a symbol alias if you\nintend to use it often.\n\n```ruby\nrequire 'shale/type'\n\nShale::Type.register(:my_integer, MyIntegerType)\n```\n\nThen you can use it like this:\n\n```ruby\nclass Person \u003c Shale::Mapper\n  attribute :age, :my_integer\nend\n```\n\n### Adapters\n\nShale uses adapters for parsing and generating documents.\nBy default Ruby's standard JSON and YAML parsers are used for handling JSON and YAML documents.\n\nYou can change it by providing your own adapter. For JSON, YAML, TOML and CSV adapter must\nimplement `.load` and `.dump` class methods.\n\n```ruby\nrequire 'shale'\nrequire 'multi_json'\n\nShale.json_adapter = MultiJson\nShale.yaml_adapter = MyYamlAdapter\n```\n\nTo handle TOML documents you have to set TOML adapter. Out of the box `Tomlib` is supported.\nShale also provides adapter for `toml-rb` parser:\n\n```ruby\nrequire 'shale'\n\n# if you want to use Tomlib\nrequire 'tomlib'\nShale.toml_adapter = Tomlib\n\n# if you want to use toml-rb\nrequire 'shale/adapter/toml_rb'\nShale.toml_adapter = Shale::Adapter::TomlRB\n```\n\nTo handle CSV documents you have to set CSV adapter. Shale provides adapter for `csv` parser:\n\n```ruby\nrequire 'shale'\nrequire 'shale/adapter/csv'\nShale.csv_adapter = Shale::Adapter::CSV\n```\n\nTo handle XML documents you have to explicitly set XML adapter.\nShale provides adapters for most popular Ruby XML parsers:\n\n:warning: **Ox doesn't support XML namespaces**\n\n```ruby\nrequire 'shale'\n\n# if you want to use REXML:\n\nrequire 'shale/adapter/rexml'\nShale.xml_adapter = Shale::Adapter::REXML\n\n# if you want to use Nokogiri:\n\nrequire 'shale/adapter/nokogiri'\nShale.xml_adapter = Shale::Adapter::Nokogiri\n\n# or if you want to use Ox:\n\nrequire 'shale/adapter/ox'\nShale.xml_adapter = Shale::Adapter::Ox\n```\n\n### Generating JSON Schema\n\n:warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported\n\nTo generate JSON Schema from your Shale data model use:\n\n```ruby\nrequire 'shale/schema'\n\nShale::Schema.to_json(\n  Person,\n  id: 'http://foo.bar/schema/person',\n  description: 'My description',\n  pretty: true\n)\n\n# =\u003e\n#\n# {\n#   \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n#   \"$id\": \"http://foo.bar/schema/person\",\n#   \"description\": \"My description\",\n#   \"$ref\": \"#/$defs/Person\",\n#   \"$defs\": {\n#     \"Address\": {\n#       \"type\": [\n#         \"object\",\n#         \"null\"\n#       ],\n#       \"properties\": {\n#         \"city\": {\n#           \"type\": [\n#             \"string\",\n#             \"null\"\n#           ]\n#         }\n#       }\n#     },\n#     \"Person\": {\n#       \"type\": \"object\",\n#       \"properties\": {\n#         \"name\": {\n#           \"type\": [\n#             \"string\",\n#             \"null\"\n#           ]\n#         },\n#         \"address\": {\n#           \"$ref\": \"#/$defs/Address\"\n#         }\n#       }\n#     }\n#   }\n# }\n```\n\nYou can also use a command line tool to do it:\n\n```\n$ shaleb -i data_model.rb -r Person -p\n```\n\nIf you want to convert your own types to JSON Schema types use:\n\n```ruby\nrequire 'shale'\nrequire 'shale/schema'\n\nclass MyEmailType \u003c Shale::Type::Value\n  ...\nend\n\nclass MyEmailJSONType \u003c Shale::Schema::JSONGenerator::Base\n  def as_type\n    { 'type' =\u003e 'string', 'format' =\u003e 'email' }\n  end\nend\n\nShale::Schema::JSONGenerator.register_json_type(MyEmailType, MyEmailJSONType)\n```\n\nTo add validation keywords to the schema, you can use a custom model and do this:\n\n```ruby\nrequire 'shale/schema'\n\nclass PersonMapper \u003c Shale::Mapper\n  model Person\n\n  attribute :first_name, :string\n  attribute :last_name, :string\n  attribute :address, :string\n  attribute :age, :integer\n\n  json do\n    properties max_properties: 5, additional_properties: false\n\n    map \"first_name\", to: :first_name, schema: { required: true }\n    map \"last_name\", to: :last_name, schema: { required: true }\n    map \"address\", to: :address, schema: { max_length: 128, description: \"Street, home number, city and country\" }\n    map \"age\", to: :age, schema: { minimum: 1, maximum: 150, description: \"Person age\" }\n  end\nend\n\nShale::Schema.to_json(\n  PersonMapper,\n  pretty: true\n)\n\n# =\u003e\n#\n# {\n#   \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n#   \"description\": \"My description\",\n#   \"$ref\": \"#/$defs/Person\",\n#   \"$defs\": {\n#     \"Person\": {\n#       \"type\": \"object\",\n#       \"properties\": {\n#         \"first_name\": {\n#           \"type\": \"string\"\n#         },\n#         \"last_name\": {\n#           \"type\": \"string\"\n#         },\n#         \"address\": {\n#           \"type\": [\n#             \"string\",\n#             \"null\"\n#           ],\n#           \"maxLength\": 128,\n#           \"description\": \"Street, home number, city and country\"\n#         },\n#         \"age\": {\n#           \"type\": [\n#             \"integer\",\n#             \"null\"\n#           ],\n#           \"minimum\": 1,\n#           \"maximum\": 150,\n#           \"description\": \"Person age\"\n#         }\n#       },\n#       \"required\": [\n#         \"first_name\",\n#         \"last_name\"\n#       ],\n#       \"maxProperties\": 5,\n#       \"additionalProperties\": false\n#     }\n#   }\n# }\n```\n\nValidation keywords are supported for all types, only the global `enum` and `const` types are not supported.\n\n### Compiling JSON Schema into Shale model\n\n:warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported\n\nTo generate Shale data model from JSON Schema use `Shale::Schema.from_json`.\nYou can pass `root_name: 'Foobar'` to change the name of the root type and\n`namespace_mapping: {}` to map schemas to Ruby modules:\n\n```ruby\nrequire 'shale/schema'\n\nschema = \u003c\u003c~SCHEMA\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"firstName\": { \"type\": \"string\" },\n    \"lastName\": { \"type\": \"string\" },\n    \"address\": { \"$ref\": \"http://bar.com\" }\n  },\n  \"$defs\": {\n    \"Address\": {\n      \"$id\": \"http://bar.com\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"street\": { \"type\": \"string\" },\n        \"city\": { \"type\": \"string\" }\n      }\n    }\n  }\n}\nSCHEMA\n\nShale::Schema.from_json(\n  [schema],\n  root_name: 'Person',\n  namespace_mapping: {\n    nil =\u003e 'Api::Foo', # default schema (without ID)\n    'http://bar.com' =\u003e 'Api::Bar',\n  }\n)\n\n# =\u003e\n#\n# {\n#   \"api/bar/address\" =\u003e \"\n#     require 'shale'\n#\n#     module Api\n#       module Bar\n#         class Address \u003c Shale::Mapper\n#           attribute :street, Shale::Type::String\n#           attribute :city, Shale::Type::String\n#\n#           json do\n#             map 'street', to: :street\n#             map 'city', to: :city\n#           end\n#         end\n#       end\n#     end\n#   \",\n#   \"api/foo/person\" =\u003e \"\n#     require 'shale'\n#\n#     require_relative '../bar/address'\n#\n#     module Api\n#       module Foo\n#         class Person \u003c Shale::Mapper\n#           attribute :first_name, Shale::Type::String\n#           attribute :last_name, Shale::Type::String\n#           attribute :address, Api::Bar::Address\n#\n#           json do\n#             map 'firstName', to: :first_name\n#             map 'lastName', to: :last_name\n#             map 'address', to: :address\n#           end\n#         end\n#       end\n#     end\n#   \"\n# }\n```\n\nYou can also use a command line tool to do it:\n\n```\n$ shaleb -c -i schema.json -r Person -m http://bar.com=Api::Bar,=Api::Foo\n```\n\n### Generating XML Schema\n\nTo generate XML Schema from your Shale data model use:\n\n```ruby\nrequire 'shale/schema'\n\nShale::Schema.to_xml(Person, pretty: true)\n\n# =\u003e\n#\n# {\n#   'schema0.xsd' =\u003e '\n#     \u003cxs:schema\n#       elementFormDefault=\"qualified\"\n#       attributeFormDefault=\"qualified\"\n#       xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n#       xmlns:foo=\"http://foo.com\"\n#     \u003e\n#       \u003cxs:import namespace=\"http://foo.com\" schemaLocation=\"schema1.xsd\"/\u003e\n#       \u003cxs:element name=\"person\" type=\"Person\"/\u003e\n#       \u003cxs:complexType name=\"Person\"\u003e\n#         \u003cxs:sequence\u003e\n#           \u003cxs:element name=\"name\" type=\"xs:string\" minOccurs=\"0\"/\u003e\n#           \u003cxs:element ref=\"foo:address\" minOccurs=\"0\"/\u003e\n#         \u003c/xs:sequence\u003e\n#       \u003c/xs:complexType\u003e\n#     \u003c/xs:schema\u003e',\n#\n#   'schema1.xsd' =\u003e '\n#     \u003cxs:schema\n#       elementFormDefault=\"qualified\"\n#       attributeFormDefault=\"qualified\"\n#       targetNamespace=\"http://foo.com\"\n#       xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n#       xmlns:foo=\"http://foo.com\"\n#     \u003e\n#       \u003cxs:element name=\"address\" type=\"foo:Address\"/\u003e\n#       \u003cxs:complexType name=\"Address\"\u003e\n#         \u003cxs:sequence\u003e\n#           \u003cxs:element name=\"city\" type=\"xs:string\" minOccurs=\"0\"/\u003e\n#         \u003c/xs:sequence\u003e\n#       \u003c/xs:complexType\u003e\n#     \u003c/xs:schema\u003e'\n# }\n```\n\nYou can also use a command line tool to do it:\n\n```\n$ shaleb -i data_model.rb -r Person -p -f xml\n```\n\nIf you want to convert your own types to XML Schema types use:\n\n```ruby\nrequire 'shale'\nrequire 'shale/schema'\n\nclass MyEmailType \u003c Shale::Type::Value\n  ...\nend\n\nShale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')\n```\n\n### Compiling XML Schema into Shale model\n\nTo generate Shale data model from XML Schema use `Shale::Schema.from_xml`.\nYou can pass `namespace_mapping: {}` to map XML namespaces to Ruby modules:\n\n```ruby\nrequire 'shale/schema'\n\nschema1 = \u003c\u003c~SCHEMA\n\u003cxs:schema\n  xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n  xmlns:bar=\"http://bar.com\"\n  elementFormDefault=\"qualified\"\n\u003e\n  \u003cxs:import namespace=\"http://bar.com\" /\u003e\n\n  \u003cxs:element name=\"Person\" type=\"Person\" /\u003e\n\n  \u003cxs:complexType name=\"Person\"\u003e\n    \u003cxs:sequence\u003e\n      \u003cxs:element name=\"Name\" type=\"xs:string\" /\u003e\n      \u003cxs:element ref=\"bar:Address\" /\u003e\n    \u003c/xs:sequence\u003e\n  \u003c/xs:complexType\u003e\n\u003c/xs:schema\u003e\nSCHEMA\n\nschema2 = \u003c\u003c~SCHEMA\n\u003cxs:schema\n  xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n  xmlns:bar=\"http://bar.com\"\n  targetNamespace=\"http://bar.com\"\n  elementFormDefault=\"qualified\"\n\u003e\n  \u003cxs:element name=\"Address\" type=\"bar:Address\" /\u003e\n\n  \u003cxs:complexType name=\"Address\"\u003e\n    \u003cxs:sequence\u003e\n      \u003cxs:element name=\"Street\" type=\"xs:string\" /\u003e\n      \u003cxs:element name=\"City\" type=\"xs:string\" /\u003e\n    \u003c/xs:sequence\u003e\n  \u003c/xs:complexType\u003e\n\u003c/xs:schema\u003e\nSCHEMA\n\nShale::Schema.from_xml(\n  [schema1, schema2],\n  namespace_mapping: {\n    nil =\u003e 'Api::Foo', # no namespace\n    'http://bar.com' =\u003e 'Api::Bar',\n  }\n)\n\n# =\u003e\n#\n# {\n#   \"api/bar/address\" =\u003e \"\n#     require 'shale'\n#\n#     module Api\n#       module Bar\n#         class Address \u003c Shale::Mapper\n#           attribute :street, Shale::Type::String\n#           attribute :city, Shale::Type::String\n#\n#           xml do\n#             root 'Address'\n#             namespace 'http://bar.com', 'bar'\n#\n#             map_element 'Street', to: :street\n#             map_element 'City', to: :city\n#           end\n#         end\n#       end\n#     end\n#   \",\n#   \"api/foo/person\" =\u003e \"\n#     require 'shale'\n#\n#     require_relative '../bar/address'\n#\n#     module Api\n#       module Foo\n#         class Person \u003c Shale::Mapper\n#           attribute :name, Shale::Type::String\n#           attribute :address, Api::Bar::Address\n#\n#           xml do\n#             root 'Person'\n#\n#             map_element 'Name', to: :name\n#             map_element 'Address', to: :address, prefix: 'bar', namespace: 'http://bar.com'\n#           end\n#         end\n#       end\n#     end\n#   \"\n# }\n```\n\nYou can also use a command line tool to do it:\n\n```\n$ shaleb -c -f xml -i schema.xml -m http://bar.com=Api::Bar,=Api::Foo\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/kgiszczak/shale.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkgiszczak%2Fshale","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkgiszczak%2Fshale","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkgiszczak%2Fshale/lists"}