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

Allow customization of JSON schema $defs key and $ref #33

Closed mdesantis closed 2 months ago

mdesantis commented 4 months ago

Suppose you have the following code:

class Person; end

class PersonMapper < Shale::Mapper
  model Person
end

require 'shale/schema'

puts Shale::Schema.to_json(
  PersonMapper,
  pretty: true
)

You get

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$ref": "#/$defs/Person",
  "$defs": {
    "Person": {
      "type": "object",
      "properties": {}
    }
  }
}

But, what if you want a $defs key and $ref calculated differently from the current logic? This is the cleanest way I've figured out so far:

def schema_with_custom_defs_key_and_ref(mapper, defs_key:)
  schema = Shale::Schema::JSONGenerator.new.as_schema(mapper)
  old_defs_key = mapper.model.name
  old_ref = schema['$ref']
  new_defs_key = defs_key
  new_ref = "#/defs/#{new_defs_key}"

  schema['$ref'] = new_ref
  schema['$defs'][new_defs_key] = schema['$defs'].delete(old_defs_key)

  schema
end

puts JSON.pretty_generate(schema_with_custom_defs_key_and_ref(PersonMapper, defs_key: 'User'))

Which gives

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$ref": "#/defs/User",
  "$defs": {
    "User": {
      "type": "object",
      "properties": {}
    }
  }
}

But it's quite cumbersome. What about something like

class PersonMapper < Shale::Mapper
  model Person

  json do
    defs_key 'User'
  end
end

Another option I guess would be to pass defs_key to as_schema, e.g.:

Shale::Schema::JSONGenerator.new.as_schema(PersonMapper, defs_key: 'User')

But I'm new to JSON schemas, I don't know which one makes more sense

kgiszczak commented 4 months ago

What you're doing is probably the best way to change refs at the moment. I'm curious, what is the use case for doing that?

mdesantis commented 4 months ago

I'm curious, what is the use case for doing that?

Basically these are my mappers:

module ShaleMappers
  class Model < Shale::Mapper
    model ::Model
    ...

Which would make the $defs key to be ShaleMappers_Model but I'd like to have just Model. Also, in case of a model namespace, I'm not enthusiast of having _ as separator, I'd like to use something else (I was even thinking of /, but I'm new to JSON schemas so I'm unsure it makes sense), so yeah stuff like this

UPDATE Actually, I was wrong about the ShaleMappers_Model point, I've just realized I miswrote the model forgetting the ::, that's why ShaleMappers_ was included in the $defs key. But the argument about the _ still remains.

mdesantis commented 4 months ago

(btw thanks for this library, I love that it doesn't have any open issues nor pull requests so much that opening one made me sad :joy: )

kgiszczak commented 4 months ago

Thanks. Like I said, for now I would stick to what you're doing currently. I might implement it in the future, but for now I don't have much free time.