Giter Club home page Giter Club logo

kartograph's Introduction

Kartograph

A Serialization / Deserialization library.

Build Status

Installation

Add this line to your application's Gemfile:

gem 'kartograph'

And then execute:

$ bundle

Usage

Kartograph makes it easy to generate and convert JSON. It's intention is to be used for API clients.

For example, if you have an object that you would like to convert to JSON for a create request to an API. You would have something similar to this:

class UserMapping
  include Kartograph::DSL

  kartograph do
    mapping User # The object we're mapping

    property :name, :email, scopes: [:create, :update]
    property :id, scopes: :read
  end
end

user = User.new(name: 'Bobby Tables')
json_for_create = UserMapping.representation_for(:create, user)

Rendering Objects or Collections as Hashes

user = User.new(name: 'PB Jelly')
users = [user]

hash = UserMapping.hash_for(:read, user)
hash_collection = UserMapping.hash_collection_for(:read, user)

Rendering Collections as JSON

user = User.new(name: 'Bobby Tables')
users = Array.new(10, user)

json = UserMapping.represent_collection_for(:read, users)

Some API's will give you the created resource back as JSON as well on a successful create. For that, you may do something like this:

response = HTTPClient.post("http://something.com/api/users", body: json_for_create)
created_user = UserMapping.extract_single(response.body, :read)

Most API's will have a way of retrieving an entire resource collection. For this you can instruct Kartograph to convert a collection.

response = HTTPClient.get("http://something.com/api/users")
users = UserMapping.extract_collection(response.body, :read)
# => [ User, User, User ]

Getting Harder

Sometimes resources will nest other properties under a key. Kartograph can handle this as well.

class UserMapping
  include Kartograph::DSL

  kartograph do
    mapping User # The object we're mapping

    property :name, scopes: [:read]

    property :comments do
      mapping Comment # The nested object we're mapping

      property :text, scopes: [:read]
      property :author, scopes: [:read]
    end
  end
end

Just like the previous examples, when you serialize this. It will include the comment block for the scope defined.

Root Keys

Kartograph can also handle the event of root keys in response bodies. For example, if you receive a response with:

{ "user": { "id": 123 } }

You could define a mapping like this:

class UserMapping
  include Kartograph::DSL

  kartograph do
    mapping User
    root_key singular: 'user', plural: 'users', scopes: [:read]
    property :id, scopes: [:read]
  end
end

This means that when you call the same thing:

response = HTTPClient.get("http://something.com/api/users")
users = UserMapping.extract_collection(response.body, :read)

It will look for the root key before trying to deserialize the JSON response. The advantage of this is it will only use the root key if there is a scope defined for it.

Including other definitions within eachother

Sometimes you might have models that are nested within eachother on responses. Or you simply want to cleanup definitions by separating concerns. Kartograph lets you do this with includes.

class UserMapping
  include Kartograph::DSL

  kartograph do
    mapping User
    property :id, scopes: [:read]
    property :comments, plural: true, include: CommentMapping
  end
end

class CommentMapping
  include Kartograph::DSL

  kartograph do
    mapping Comment
    property :id, scopes: [:read]
    property :text, scopes: [:read]
  end
end

Scope blocks

Sometimes adding scopes to all properties can be tedious, to avoid that, you can define properties within a scope block.

class UserMapping
  include Kartograph::DSL

  kartograph do
    scoped :read do
      property :name
      property :id
      property :email, key: 'email_address' # The JSON returned has the key of email_address, our property is called email however.
    end

    scoped :update, :create do
      property :name
      property :email, key: 'email_address'
    end
  end
end

Now when JSON includes comments for a user, it will know how to map the comments using the provided Kartograph definition.


Caching

Kartograph has the option to cache certain serializations, determined by the way you setup the key.

class UserMapping
  include Kartograph::DSL

  kartograph do
    cache { Rails.cache } # As long as this respond to #fetch(key_name, options = {}, &block) it will work
    cache_key { |object| object.cache_key }

    end
  end
end

Contributing

  1. Fork it ( https://github.com/digitaloceancloud/kartograph/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

kartograph's People

Contributors

andrewsomething avatar aybabtme avatar bentranter avatar bobbytables avatar firefishy avatar ivanvanderbyl avatar nanzhong avatar phillbaker avatar vinibaggio avatar wwkeyboard avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kartograph's Issues

A way to avoid hard checks?

Right now. in order to generate a JSON using a mapping, I must pass an object that respond to the corresponding properties, is there a way to avoid these errors? More specifically what I want is being able to do something like this

class Mapper
  include Kartograph::DSL

  kartograph do
    scoped :read do
      property :title
      property :description
    end
  end
end

Mapper.representation_for(OpenStruct.new(title: 'I have  a title but no description'), :read)

and this would return: => "{\"title\":\"I have a title but no description\",\"description\":null}"

Is this something that would fit in the scope of the gem? I could try to contribute this fix if this is something that makes sense.

Allow multiple root keys or nested data (not necessarily nested models)

Maybe there's a better way to handle this, but right now it seems a root key that's multiple levels down is inaccessible. See this example json:

{
  "code": 0,
  "message": "success",
  "data": {
    "monitors": [{
      "name": "TYPE_ONE",
      "status": 1
    },{
      "name": "TYPE_TWO",
      "status": 1
    }],
    "monitor_groups": [{
      "status": 1,
      "monitors": [{
        "name": "DIFFERENT_TYPE",
        "status": 1
      }]
    }]
  }
}

For example if the values after doc["data"]["monitors"] are required (e.g. an array of TYPE_ONE and TYPE_TWO objects) this statement won't find the correct key
root_key plural: "monitors", singular: "monitors", scopes: [:read]
The ability to set another level of root keys such as the following would be great:
root_key_1 plural: "data", singular: "data", scopes: [:read]
root_key_2 plural: "monitors", singular: "monitor", scopes: [:read]
Alternatively to have the root key be able to start multiple levels into the json (although then there might be problems with similarly named keys)

Empty string breaks expected child object mapping

Before I begin, I understand that JSON has a null value representation and the API I am working with is doing things "wrong," but I am stuck with them. Here's what I'm dealing with in a typical API response:

json = '{
  "Emails": {
    "ContactEmail": {
      "address": "[email protected]",
      "useType": "Primary"
    }
  }
}'

json_empty = '{
  "Emails": ""
}'

Depending on whether or not the child attribute of ContactEmail is populated or not (if a customer has an email address in the system), I either get an array or an empty string. Kartograph currently expects something it can understand and map, or a nil value.

My workaround for this was to add an return if object.empty? check to the existing object.nil? check in /lib/kartograph/property.rb:34 to avoid seeing a has_key?' for "":String (NoMethodError) during mapping the ContactEmail (child).

This seems like the exact sort of hack that will have unintended consequences, so before I submit a pull request I wanted to put this use case out there and see if you all had any thoughts. Is this sort "bad" JSON something you want to be flexible enough to support?

Note: I have no intention of trying to support this JSON syntax for create operations. I'm just trying to read in data.

Support apis that use idential root_key for plural and singular attributes

This is an example from a retail point of sale API I was using Kartograph to wrap last year:

User with one email address:

{
    "Emails": {
        "ContactEmail": {
            "address": "[email protected]",
            "useType": "Primary"
        }
    }
}

User with two, or more, addresses:

{
    "Emails": {
        "ContactEmail": [{
            "address": "[email protected]",
            "useType": "Primary"
        }, {
            "address": "[email protected]",
            "useType": "Secondary"
        }]
    }
}

Here ContactEmail is used as the root key in both the plural and singular use cases. If processed, you will run into a has_key? for Array exception generate in the property.rb value_from method.

I used a hack to get around the exceptions that threw away additional array members effectively forcing my ContactEmail property to be singular, but I think a better approach is to allow Kartograph to force plural where specified. That is, if I specify plural in a model that Kartograph should make a best attempt to cast the read property value as an array.

It's possible that Kartograph is not the place to make this effort and that plural could be removed in favor of letting Virtus mappings handle special coercion use cases, but I thought I would post this here in case any one had better ideas.

undefined method `+' for :read:Symbol (NoMethodError)

Hi, I was trying to use kartograph a little bit, here is my code:

require 'kartograph'

class User
  attr_accessor :name, :email, :id

  def initialize(name:'name', email:'email', id:'id')
    @name = name
    @email = email
    @id = id
  end

end

class UserMapping
  include Kartograph::DSL

  kartograph do
    mapping User # The object we're mapping

    property :name, :email, scopes: [:create, :update]
    property :id, scopes: :read
  end
end

user = User.new(name: 'Bobby Tables')
json_for_create = UserMapping.representation_for(:create, user)

it returns following error:

/home/mateusz/.rvm/gems/ruby-2.3.0/gems/kartograph-0.2.4/lib/kartograph/map.rb:14:in `property': undefined method `+' for :read:Symbol (NoMethodError)
  from /home/mateusz/kartograph_testing.rb:14:in `block in <class:UserMapping>'
  from /home/mateusz/.rvm/gems/ruby-2.3.0/gems/kartograph-0.2.4/lib/kartograph/dsl.rb:12:in `instance_eval'
  from /home/mateusz/.rvm/gems/ruby-2.3.0/gems/kartograph-0.2.4/lib/kartograph/dsl.rb:12:in `kartograph'
  from /home/mateusz/kartograph_testing.rb:10:in `<class:UserMapping>'
  from /home/mateusz/kartograph_testing.rb:7:in `<main>'

Artist draws values for all properties, even if they are not set

When creating a representation for an object, the artist draws all properties, including them even if they are null. This interferes with some endpoints, as they may try to lookup a value if the key is present. For example, file may be an optional property, but if the key is present the server may try to open that file and return an error.

{name:"test",file:null}

The artist should exclude properties without values.

Specifing the scope that another mapping is going to use.

Is there a way to specify a scope that another mapping is gonna use? something like:

class UserMapping
  include Kartograph::DSL

  kartograph do
    mapping User
    property :id, scopes: [:read]
    property :comments, scopes: [:read], plural: true, include: [CommentMapping, :update]
  end
end

class CommentMapping
  include Kartograph::DSL

  kartograph do
    mapping Comment
    property :id, scopes: [:read]
    property :text, scopes: [:update]
  end
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.