6 ms·
Show HN: Shale – a Ruby object mapper and serializer for JSON, YAML and XML
- beerkg 4y agoHi, I released Shale, a Ruby gem that allows you to parse JSON, YAML and XML and convert it into Ruby data structures, as well as serialize your Ruby data model to JSON, YAML or XML. Features: - convert JSON, XML or YAML into Ruby data model - serialize data model to JSON, XML or YAML - generate JSON and XML Schema from Ruby models - compile JSON Schema into Ruby models (compiling XML Schema is a work in progress) A quick example so you can get a feel of it: require 'shale' class Address < Shale::Mapper attribute :street, Shale::Type::String attribute :city, Shale::Type::String end class Person < Shale::Mapper attribute :first_name, Shale::Type::String attribute :last_name, Shale::Type::String attribute :address, Address end # parse data and convert it into Ruby data model person = Person.from_json(<<~JSON) # or .from_xml / .from_yaml { "first_name": "John", "last_name": "Doe", "address": { "street": "Oxford Street", "city": "London" } } JSON # It will give you # => # #<Person:0xa0a4 # @address=#<Address:0xa0a6 # @city="London", # @street="Oxford Street", # @zip="E1 6AN">, # @age=50, # @first_name="John", # @hobbies=["Singing", "Dancing"], # @last_name="Doe", # @married=false> # serialize Ruby data model to JSON Person.new( first_name: 'John', last_name: 'Doe', address: Address.new(street: 'Oxford Street', city: 'London') ).to_json # or .to_xml / .to_yaml Source code is available on GitHub: https://github.com/kgiszczak/shale https://github.com/kgiszczak/shale
- WJW 4y agoIn the last example, where does it find the values for the `married`, `age`, `zip` and `hobbies` attributes? They are not present in the JSON string?
- beerkg 4y agoAh, I messed up the example, Person class definition should look like this: class Person < Shale::Mapper attribute :first_name, Shale::Type::String attribute :last_name, Shale::Type::String attribute :age, Shale::Type::Integer attribute :married, Shale::Type::Boolean, default: false attribute :hobbies, Shale::Type::String, collection: true attribute :address, Address end And the JSON used for parsing also should contain those atttributes, like: { "first_name": "John", "last_name": "Doe", "age": 30, "married": false, "hobbies": ["Singing", "Dancing"], "address": { "street": "Oxford Street", "city": "London" } }
- danychok 4y agoJust noticed when sharing the site link - the summary reads: Vue-powered Static Site Generator. A bit misleading. <meta name="description" content="Vue-powered Static Site Generator"> Kudos for choosing Vue tho =)
- beerkg 4y agoDocumentation site was based on https://vuepress.vuejs.org/ https://vuepress.vuejs.org/ but it evolved so much I dropped Vue all together and wen't with plain HTML instead. I must have left that meta tag from the early days. Regarding Vue I use it daily at my job, great library :)
- sam0x17 4y agoHey this is a very cool project! When you were developing it, I'm curious if you took any special security precautions in your design of this project, seeing how XML/JSON/YAML serialization and de-serialization are the topic of many high profile CVEs, particularly in the Ruby community?
- beerkg 4y agoShale uses Ruby's standard library parsers out of the box, so if you keep your Ruby up to date with security updates you will be good. Also others in this thread suggested to set minimal version on dependencies, so I'll probably do that in the future version.
- alipitch 4y agoWhen using the shale gem, how would you avoid the mass assignment problem? Is there a configuration, or a way of using the shale gem to avoid it? CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes <https://cwe.mitre.org/data/definitions/915.html https://cwe.mitre.org/data/definitions/915.html> (Ruby on Rails Mass assignment bug)
- DANK_YACHT 4y agoThis seems like programmer error. Don't put restricted fields into types you're deserializing off the wire. It's like accepting user input and directly inserting it into a database without any validation.
- beerkg 4y agoIf you don't define attributes explicitly on the model, Shale will ignore them. Regarding attributes that you defined but still don't want to be assigned, you should probably filter them before passing them to Shale, or alternatively filter them with Shale before passing them further down the stack (e.g to ActiveRecord)
- vasilakisfil 4y agoSerialization/deserialization is such an important part of web development, I have no idea why Rails includes the ancient JBuilder (and very slow since it goes through templating) library, instead of investing in a proper library. Let alone deserializing which is equally important.. I think the API Shale provides is pretty sane. I would probably use it in my next Ruby/Rails project. I don't like the fact that Nokogiri is included by default, it would be nice to declare a core type, and then bring in what you need (JSON, XML, YAML) as a different gem. But that's not a deal breaker for me. I have created my own serializers in the past (SimpleAMS[1]) because I really detested AMS, no offence to AMS contributors, but AMS library should just die. Rails, and way more importantly Ruby, should come up with an "official" serializers/deserializers library that is flexible enough, rock solid and fast. For instance I had done some benchmarking among common serializer libraries [2] and AMS was crazy slow, without providing much flexibility, really (meaning, slowness is not justified). Others were faster, but were supporting only one JSON spec format (like jsonapi-rb). I am wondering where shale stands. Another thing is that most serialization libraries seem to have ActiveSupport as a main dependency (not shale though) which I think is a bit too much, and actually has a performance hit on the methods it provides. I really think that Ruby community can do better here ? [1] https://github.com/vasilakisfil/SimpleAMS https://github.com/vasilakisfil/SimpleAMS [2] https://vasilakisfil.social/blog/2020/01/20/modern-ruby-serializers https://vasilakisfil.social/blog/2020/01/20/modern-ruby-seri... (scroll towards the end for benchmarks)
- beerkg 4y agoI'm glad you like it. One clarification - Nokogiri is not required by default, you have to explicitly require "shale/adapter/nokogiri" to use it. If you don't Shale will use REXML which comes from Ruby's standard library.
- zwp 4y agoRexml has been gemified. Shale's gemspec doesn't require a specific version of rexml and rexml<3.2.5 is vulnerable to CVE-2021-28965. I just checked Ubuntu 20.04 LTS and got Ruby 2.7 with rexml 3.2.3 by default so this seems like a realistic concern and it would be safer if shale required a minimum rexml version. See http://www.ruby-lang.org/en/news/2021/04/05/xml-round-trip-vulnerability-in-rexml-cve-2021-28965/ http://www.ruby-lang.org/en/news/2021/04/05/xml-round-trip-v...
- codesnik 4y agoOne of the things that keeps being repeated in ruby land is that domain objects are usually married to storage/serialisation method. At some point of application maturity you'll need some other method of serialisation, some other type casting or conversion logic for your form or something else, but by that time a lot of surrounding code would depend on implicit logic of the original base library. ActiveRecord does this, and your library does it too. Object mappers which can initialize or serialize instances of other classes, including PORO, are much more versatile and future-proof. And API for doing that could look almost the same as yours.
- TSiege 4y agoGreat point. I feel like this is an often ignored advantage of JS/TS projects. Most often data is passed around as POJOs. It's dead simple and easy to duplicate, serialize, and mutate
- arthurcolle 4y agoPOJSOs? :D
- beerkg 4y agoI totally agree with your points, but this approach has one big advantage - it's dead simple - define attributes and mapping and you're good to go.
- codesnik 4y agoYou don't have to sacrifice that simplicity, actually. (And I insist on that simplicity being a wrong type, it'll bite users of your library basically right away, when they try to use it for anything apart from storage/serialisation) But you can just give an upgrade path! consider something like this: class Address attr_accessor :street, :city end class Person attr_accessor :address end class AddressMapper < Shale::Mapper mapped_class Address attribute :street, Shale::Type::String attribute :city, Shale::Type::String end class PersonMapper < Shale::Mapper mapped_class Person attribute :address, AddressMapper end # use like this PersonMapper.from_xml("...."); PersonMapper.to_xml(person) and then, for _dead_ simplicity, you can add another method generate_mapped_class "Person" which will define that PORO class for user for extra DRYness. API is basically the same, no repetition, but amount of rewrite with new requirements is drastically less. I'm not asking you to rewrite your library, and I probably won't write and release mine, just saying that considering future self isn't that hard. And yeah, it's a bit of a rant about ActiveRecord from user of Rails, since 2006.
- rufugee 4y agoGlad to see folks actively pushing things in the Ruby space further. I've said it before, but I recently returned to Ruby and Rails after many years away, and my productivity has reached levels I couldn't imagine. Subjective for sure, but ruby is a beautiful fun language, and rails has everything (especially now with https://hotwired.dev https://hotwired.dev) that a single founder needs.
- geonic 4y agoExactly my experience. Rails allows me to build quickly and iterate even faster. Highly recommended. Hotwire is pretty cool, too. I‘ve built an action palette type of dialog with keyboard navigation without any stateful JavaScript (except for the cursor).
- weatherlite 4y agoI'm currently doing both Rails and Go, it's just different worlds. I'm a Go noob so it's not a fair comparison but still - I did Django, Node, etc etc and Go is just miles behind anything productive.
- konart 4y agoYou are comparing a language to two complete frameworks and a runtime. Go can be extremely productive but it's definitely not a great choice if you need to create a web app over a weekend. RoR, Django etc have ready solutions for things like authorization\authentication, administration tools, oauth... Not to mention that 'framework' assumes some sort of contracts so that all thing build for the framework in question can talk to each other. Go is a good choice if you need to build a custom solution for your needs. Not if you are looking for a set of building blocks you have to configure for your task.
- weatherlite 4y agoAm using Chi and GORM for what it's worth.
- 4y ago
- forgingahead 4y agoThanks for this! Definitely going to use this for one of our big projects. *Edit: nice docs site as well - what are you using for it?
- beerkg 4y agoIt's a custom template I created (based on https://vuepress.vuejs.org/ https://vuepress.vuejs.org/), because I couldn't find anything that simple. The source code is available on https://github.com/kgiszczak/shale-website https://github.com/kgiszczak/shale-website Interactive examples are powered by https://opalrb.com/ https://opalrb.com/
- pmontra 4y agoIt would be great to be able to generate the Ruby models from XML Schema Definition files (.xsd) No mistakes and a huge time saver.
- april_22 4y agoThanks for pushing the Ruby space further!
- Spone 4y agoNice library with a very approachable documentation, congrats! I'll probably give it a go to replace my current implementation using nokogiri-happymapper (https://github.com/mvz/happymapper https://github.com/mvz/happymapper)
- beerkg 4y agoHappyMapper was actually an inspiration for Shale. If it had support for JSON, Shale probably wouldn't be created :)
- gosukiwi 4y agoI like this idea, I remember seeing something similar in Trailblazer. But basically you just define your models once, and then you can transform them into different formats, and have them play nicely with ActiveRecord as well. Pretty cool :)
- nightpool 4y agoThis library looks great for those using it, but I wish the situation for "ActiveRecord model -> JSON representation" in open-source libraries was better. This library seems to be overkill for that, since you'll almost always want completely separate code for "deserializing" attribute updates from a request, and it requires you to specify the type of every single property. ActiveModel::Serializer was great while it lasted, but it's unmaintained and missing a lot of features. Blueprinter seems a lot less battle-tested and may have performance problems. Last I looked, almost no library easily supports eager-loading. Is this right? I feel like I must be missing something. How do people render their models in modern Rails apps?
- rognjen 4y agoNice, it seems like a generic version of grape-entity https://github.com/ruby-grape/grape-entity https://github.com/ruby-grape/grape-entity