Giter Club home page Giter Club logo

oj_serializers's Introduction

Oj Serializers

Build Status Maintainability Test Coverage Gem Version License

Faster JSON serializers for Ruby, built on top of the powerful oj library.

Why? 🤔

ActiveModel::Serializer has a nice DSL, but it allocates many objects leading to memory bloat, time spent on GC, and lower performance.

Oj::Serializer provides a similar API, with better performance.

Learn more about how this library achieves its performance.

Features ⚡️

Installation 💿

Add this line to your application's Gemfile:

gem 'oj_serializers'

And then run:

$ bundle install

Usage 🚀

You can define a serializer by subclassing Oj::Serializer, and specify which attributes should be serialized.

class AlbumSerializer < Oj::Serializer
  attributes :name, :genres

  attribute :release do
    album.release_date.strftime('%B %d, %Y')
  end

  has_many :songs, serializer: SongSerializer
end
Example Output
{
  name: "Abraxas",
  genres: [
    "Pyschodelic Rock",
    "Blues Rock",
    "Jazz Fusion",
    "Latin Rock",
  ],
  release: "September 23, 1970",
  songs: [
    {
      track: 1,
      name: "Sing Winds, Crying Beasts",
      composers: ["Michael Carabello"],
    },
    {
      track: 2,
      name: "Black Magic Woman / Gypsy Queen",
      composers: ["Peter Green", "Gábor Szabó"],
    },
    {
      track: 3,
      name: "Oye como va",
      composers: ["Tito Puente"],
    },
    {
      track: 4,
      name: "Incident at Neshabur",
      composers: ["Alberto Gianquinto", "Carlos Santana"],
    },
    {
      track: 5,
      name: "Se acabó",
      composers: ["José Areas"],
    },
    {
      track: 6,
      name: "Mother's Daughter",
      composers: ["Gregg Rolie"],
    },
    {
      track: 7,
      name: "Samba pa ti",
      composers: ["Santana"],
    },
    {
      track: 8,
      name: "Hope You're Feeling Better",
      composers: ["Rolie"],
    },
    {
      track: 9,
      name: "El Nicoya",
      composers: ["Areas"],
    },
  ],
}

You can then use your new serializer to render an object or collection:

class AlbumsController < ApplicationController
  def show
    render json: AlbumSerializer.one(album)
  end

  def index
    render json: { albums: AlbumSerializer.many(albums) }
  end
end
Active Model Serializers style
require "oj_serializers/sugar" # In an initializer

class AlbumsController < ApplicationController
  def show
    render json: album, serializer: AlbumSerializer
  end
  
  def index
    render json: albums, root: :albums, each_serializer: AlbumSerializer
  end
end

Rendering 🖨

Use one to serialize objects, and many to serialize enumerables:

render json: {
  favorite_album: AlbumSerializer.one(album),
  purchased_albums: AlbumSerializer.many(albums),
}

Serializers can be rendered arrays, hashes, or even inside ActiveModel::Serializer by using a method in the serializer, making it very easy to combine with other libraries and migrate incrementally.

render is a shortcut for one and many:

render json: {
  favorite_album: AlbumSerializer.render(album),
  purchased_albums: AlbumSerializer.render(albums),
}

Attributes DSL 🪄

Specify which attributes should be rendered by calling a method in the object to serialize.

class PlayerSerializer < Oj::Serializer
  attributes :first_name, :last_name, :full_name
end

You can serialize custom values by specifying that a method is an attribute:

class PlayerSerializer < Oj::Serializer
  attribute :name do
    "#{player.first_name} #{player.last_name}"
  end

  # or

  attribute
  def name
    "#{player.first_name} #{player.last_name}"
  end
end

Note

In this example, player was inferred from PlayerSerializer.

You can customize this by using object_as.

Associations 🔗

Use has_one to serialize individual objects, and has_many to serialize a collection.

You must specificy which serializer to use with the serializer option.

class SongSerializer < Oj::Serializer
  has_one :album, serializer: AlbumSerializer
  has_many :composers, serializer: ComposerSerializer
end

Specify a different value for the association by providing a block:

class SongSerializer < Oj::Serializer
  has_one :album, serializer: AlbumSerializer do
    Album.find_by(song_ids: song.id)
  end
end

In case you need to pass options, you can call the serializer manually:

class SongSerializer < Oj::Serializer
  attribute :album do
    AlbumSerializer.one(song.album, for_song: song)
  end
end

Aliasing or renaming attributes ↔️

You can pass as when defining an attribute or association to serialize it using a different key:

class SongSerializer < Oj::Serializer
  has_one :album, as: :first_release, serializer: AlbumSerializer

  attributes title: {as: :name}

  # or as a shortcut
  attributes title: :name
end

Conditional attributes ❔

You can render attributes and associations conditionally by using :if.

class PlayerSerializer < Oj::Serializer
  attributes :first_name, :last_name, if: -> { player.display_name? }

  has_one :album, serializer: AlbumSerializer, if: -> { player.album }
end

This is useful in cases where you don't want to null values to be in the response.

Advanced Usage 🧙‍♂️

Using a different alias for the internal object

In most cases, the default alias for the object will be convenient enough.

However, if you would like to specify it manually, use object_as:

class DiscographySerializer < Oj::Serializer
  object_as :artist

  # Now we can use `artist` instead of `object` or `discography`.
  attribute
  def latest_albums
    artist.albums.desc(:year)
  end
end

Identifier attributes

The identifier method allows you to only include an identifier if the record or document has been persisted.

class AlbumSerializer < Oj::Serializer
  identifier

  # or if it's a different field
  identifier :uuid
end

Additionally, identifier fields are always rendered first, even when sorting fields alphabetically.

Transforming attribute keys 🗝

When serialized data will be consumed from a client language that has different naming conventions, it can be convenient to transform keys accordingly.

For example, when rendering an API to be consumed from the browser via JavaScript, where properties are traditionally named using camel case.

Use transform_keys to handle that conversion.

class BaseSerializer < Oj::Serializer
  transform_keys :camelize

  # shortcut for
  transform_keys -> (key) { key.to_s.camelize(:lower) }
end

This has no performance impact, as keys will be transformed at load time.

Sorting attributes 📶

By default attributes are rendered in the order they are defined.

If you would like to sort attributes alphabetically, you can specify it at a serializer level:

class BaseSerializer < Oj::Serializer
  sort_attributes_by :name # or a Proc
end

This has no performance impact, as attributes will be sorted at load time.

Path helpers 🛣

In case you need to access path helpers in your serializers, you can use the following:

class BaseSerializer < Oj::Serializer
  include Rails.application.routes.url_helpers

  def default_url_options
    Rails.application.routes.default_url_options
  end
end

One slight variation that might make it easier to maintain in the long term is to use a separate singleton service to provide the url helpers and options, and make it available as urls.

Generating TypeScript automatically 🤖

It's easy for the backend and the frontend to become out of sync. Traditionally, preventing bugs requires writing extensive integration tests.

TypeScript is a great tool to catch this kind of bugs and mistakes, as it can detect incorrect usages and missing fields, but writing types manually is cumbersome, and they can become stale over time, giving a false sense of confidence.

types_from_serializers extends this library to allow embedding type information, as well as inferring types from the SQL schema when available, and uses this information to automatically generate TypeScript interfaces from your serializers.

As a result, it's posible to easily detect mismatches between the backend and the frontend, as well as make the fields more discoverable and provide great autocompletion in the frontend, without having to manually write the types.

Composing serializers 🧱

There are three options to compose serializers: inheritance, mixins, and flat_one.

Use flat_one to include all attributes from a different serializer:

class AttachmentSerializer < BaseSerializer
  identifier

  class BlobSerializer < BaseSerializer
    attributes :filename, :byte_size, :content_type, :created_at
  end

  flat_one :blob, serializer: BlobSerializer
end

Think of it as has_one without a "root", all the attributes are added directly.

Example Output
{
  id: 5,
  filename: "image.jpg,
  byte_size: 256074,
  content_type: "image/jpeg",
  created_at: "2022-08-04T17:25:12.637-07:00",
}

This is especially convenient when using types_from_serializers, as it enables automatic type inference for the included attributes.

Memoization & local state

Serializers are designed to be stateless so that an instanced can be reused, but sometimes it's convenient to store intermediate calculations.

Use memo for memoization and storing temporary information.

class DownloadSerializer < Oj::Serializer
  attributes :filename, :size

  attribute
  def progress
    "#{ last_event&.progress || 0 }%"
  end

private

  def last_event
    memo.fetch(:last_event) {
      download.events.desc(:created_at).first
    }
  end
end

hash_attributes 🚀

Very convenient when serializing Hash-like structures, this strategy uses the [] operator.

class PersonSerializer < Oj::Serializer
  hash_attributes 'first_name', :last_name
end

PersonSerializer.one('first_name' => 'Mary', :middle_name => 'Jane', :last_name => 'Watson')
# {first_name: "Mary", last_name: "Watson"}

mongo_attributes 🚀

Reads data directly from attributes in a Mongoid document.

By skipping type casting, coercion, and defaults, it achieves the best performance.

Although there are some downsides, depending on how consistent your schema is, and which kind of consumer the API has, it can be really powerful.

class AlbumSerializer < Oj::Serializer
  mongo_attributes :id, :name
end

Caching 📦

Usually rendering is so fast that turning caching on can be slower.

However, in cases of deeply nested structures, unpredictable query patterns, or methods that take a long time to run, caching can improve performance.

To enable caching, use cached, which calls cache_key in the object:

class CachedUserSerializer < UserSerializer
  cached
end

You can also provide a lambda to cached_with_key to define a custom key:

class CachedUserSerializer < UserSerializer
  cached_with_key ->(user) {
    "#{ user.id }/#{ user.current_sign_in_at }"
  }
end

It will leverage fetch_multi when serializing a collection with many or has_many, to minimize the amount of round trips needed to read and write all items to cache.

This works specially well if your cache store also supports write_multi.

Writing to JSON

In some corner cases it might be faster to serialize using a Oj::StringWriter, which you can access by using one_as_json and many_as_json.

Alternatively, you can toggle this mode at a serializer level by using default_format :json, or configure it globally from your base serializer:

class BaseSerializer < Oj::Serializer
  default_format :json
end

This will change the default shortcuts (render, one, one_if, and many), so that the serializer writes directly to JSON instead of returning a Hash.

Even when using this mode, you can still use rendered values inside arrays, hashes, and other serializers, thanks to the raw_json extensions.

Example Output
{
  "name": "Abraxas",
  "genres": [
    "Pyschodelic Rock",
    "Blues Rock",
    "Jazz Fusion",
    "Latin Rock"
  ],
  "release": "September 23, 1970",
  "songs": [
    {
      "track": 1,
      "name": "Sing Winds, Crying Beasts",
      "composers": [
        "Michael Carabello"
      ]
    },
    {
      "track": 2,
      "name": "Black Magic Woman / Gypsy Queen",
      "composers": [
        "Peter Green",
        "Gábor Szabó"
      ]
    },
    {
      "track": 3,
      "name": "Oye como va",
      "composers": [
        "Tito Puente"
      ]
    },
    {
      "track": 4,
      "name": "Incident at Neshabur",
      "composers": [
        "Alberto Gianquinto",
        "Carlos Santana"
      ]
    },
    {
      "track": 5,
      "name": "Se acabó",
      "composers": [
        "José Areas"
      ]
    },
    {
      "track": 6,
      "name": "Mother's Daughter",
      "composers": [
        "Gregg Rolie"
      ]
    },
    {
      "track": 7,
      "name": "Samba pa ti",
      "composers": [
        "Santana"
      ]
    },
    {
      "track": 8,
      "name": "Hope You're Feeling Better",
      "composers": [
        "Rolie"
      ]
    },
    {
      "track": 9,
      "name": "El Nicoya",
      "composers": [
        "Areas"
      ]
    }
  ]
}

Design 📐

Unlike ActiveModel::Serializer, which builds a Hash that then gets encoded to JSON, this implementation can use Oj::StringWriter to write JSON directly, greatly reducing the overhead of allocating and garbage collecting the hashes.

It also allocates a single instance per serializer class, which makes it easy to use, while keeping memory usage under control.

The internal design is simple and extensible, and because the library is written in Ruby, creating new serialization strategies requires very little code. Please open a Discussion if you need help 😃

Comparison with other libraries

ActiveModel::Serializer instantiates one serializer object per item to be serialized.

Other libraries such as blueprinter jsonapi-serializer evaluate serializers in the context of a class instead of an instance of a class. The downside is that you can't use instance methods or local memoization, and any mixins must be applied to the class itself.

panko-serializer also uses Oj::StringWriter, but it has the big downside of having to own the entire render tree. Putting a serializer inside a Hash or an Active Model Serializer and serializing that to JSON doesn't work, making a gradual migration harder to achieve. Also, it's optimized for Active Record but I needed good Mongoid support.

Oj::Serializer combines some of these ideas, by using instances, but reusing them to avoid object allocations. Serializing 10,000 items instantiates a single serializer. Unlike panko-serializer, it doesn't suffer from double encoding problems so it's easier to use.

Follow this discussion to find out more about the raw_json extensions that made this high level of interoperability possible.

As a result, migrating from active_model_serializers is relatively straightforward because instance methods, inheritance, and mixins work as usual.

Benchmarks 📊

This library includes some benchmarks to compare performance with similar libraries.

See this pull request for a quick comparison, or check the CI to see the latest results.

Migrating from other libraries

Please refer to the migration guide for a full discussion of the compatibility modes available to make it easier to migrate from active_model_serializers and similar libraries.

Formatting 📏

Even though most of the examples above use a single-line style to be succint, I highly recommend writing one attribute per line, sorting them alphabetically (most editors can do it for you), and always using a trailing comma.

class AlbumSerializer < Oj::Serializer
  attributes(
    :genres,
    :name,
    :release_date,
  )
end

It will make things clearer, minimize the amount of git conflicts, and keep the history a lot cleaner and more meaningful when using git blame.

Special Thanks 🙏

This library wouldn't be possible without the wonderful and performant oj library. Thanks Peter! 😃

Also, thanks to the libraries that inspired this one:

License

The gem is available as open source under the terms of the MIT License.

oj_serializers's People

Contributors

elmassimo 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

oj_serializers's Issues

Attributes order by :definition doesn't work correctly with more than 10 attributes in a serializer

Hello, I'm migrating a project from active_model_serializer to improve speed of json generation and noticed that attribute sorting doesn't work correctly when my new oj serializer has more than 10 attributes.

I know it is not important in most situations but in this case, it is crucial to preserve the existing order of attributes in the output not to confuse customers.

I have this list of attributes

    attributes :id,
      :prefix,
      :first_name,
      :middle_name,
      :last_name,
      :suffix,
      :nickname,
      :name,
      :job_title,
      :type,
      :address

    flat_one :creator, serializer: Shared::CreatorSerializer

so the correct attributes list in the order of definition is:

[49] pry(main)> attributes = Exports::ContactOjSerializer.send(:_attributes)

=> { 
  "id" => { value_from: "id", attribute: :method, identifier: true },
  "prefix" => { value_from: "prefix", attribute: :method },
  "first_name" => { value_from: "first_name", attribute: :method },
  "middle_name" => { value_from: "middle_name", attribute: :method },
  "last_name" => { value_from: "last_name", attribute: :method },
  "suffix" => { value_from: "suffix", attribute: :method },
  "nickname" => { value_from: "nickname", attribute: :method },
  "name" => { value_from: "name", attribute: :method },
  "job_title" => { value_from: "job_title", attribute: :method },
  "type" => { value_from: "type", attribute: :method },
  "address" => { value_from: "address", attribute: :method },
  "creator11" => { value_from: "creator", association: :flat,
                   serializer: Exports::Shared::CreatorSerializer } 
}

however, after calling this method it becomes incorrect:

def prepare_attributes(transform_keys: try(:_transform_keys), sort_by: try(:_sort_attributes_by))
attributes = _attributes
attributes = attributes.transform_keys(&transform_keys) if transform_keys
if sort_by == :name
sort_by = ->(name, options, _) { options[:identifier] ? "__#{name}" : name }
elsif !sort_by || sort_by == :definition
sort_by = ->(name, options, index) { options[:identifier] ? "__#{name}" : "zzz#{index}" }
end
attributes.sort_by.with_index { |(name, options), index| sort_by.call(name, options, index) }.to_h
end

and looks like this (address and creator11 were moved to the wrong places):

[45] pry(main)> Exports::ContactOjSerializer.send(:prepare_attributes)
=> {
  "id" => { value_from: "id", attribute: :method, identifier: true },
  "prefix" => { value_from: "prefix", attribute: :method },
  "address" => { value_from: "address", attribute: :method },
  "creator11" => { value_from: "creator", association: :flat,
                   serializer: Exports::Shared::CreatorSerializer },
  "first_name" => { value_from: "first_name", attribute: :method },
  "middle_name" => { value_from: "middle_name", attribute: :method },
  "last_name" => { value_from: "last_name", attribute: :method },
  "suffix" => { value_from: "suffix", attribute: :method },
  "nickname" => { value_from: "nickname", attribute: :method },
  "name" => { value_from: "name", attribute: :method },
  "job_title" => { value_from: "job_title", attribute: :method },
  "type" => { value_from: "type", attribute: :method }
}

I debugged it and found that this lambda:

sort_by = ->(name, options, index) { options[:identifier] ? "__#{name}" : "zzz#{index}" }

internally builds such array of conditions for sorting:

["__id", "zzz1", "zzz2", "zzz3", "zzz4", "zzz5", "zzz6", "zzz7", "zzz8", "zzz9", "zzz10", "zzz11"]

however, if we call sort on it it makes an unobvious modification because of the way ruby handles strings sorting:

[56] pry(main)> a.sort
=> ["__id", "zzz1", "zzz10", "zzz11", "zzz2", "zzz3", "zzz4", "zzz5", "zzz6", "zzz7", "zzz8", "zzz9"]

Which leads to the incorrect order of attributes in the output.


What if we modify that lambda like this:

sort_by = ->(name, options, index) { options[:identifier] ? index * -1 : index }

So the IDs will come first after sorting but we do use the index as a number which keeps the order of other attributes correct.

Although, it has one disadvantage. If there is more than one it will set the identifiers in the inverse order of their positions instead of sorting them by name. But I guess it should be a rare case 🤔
So if that is critical maybe there should be a better solution.

Ideally, from sort_by == :definition I'd expect to not make any changes in the order at all and to have a separate sort_by kind for putting IDs to the first place when needed, e.g. sort_by == :identifiers_first.
But to keep the backward compatibility, might just make the suggested (or somehow improved) change.

What do you think?

For me, it is important to fix this as soon as possible, so I can try to prepare a PR with this fix if that's okay.

non-cacheable fields

Hi ElMassimo, thanks for your work, amazing and fast lib!

I have a few questions:

  1. How do I make fields in the serializer that are always obtained by querying a value rather than a cached version of the value?
class MixSerializer < Oj::Serializer
  has_one     :user, serializer: UserListItemSerializer

  attributes  :id, 
              :field_that_never_been_cached,
              :title


  attribute \
  def field_that_never_been_cached
    get_updated_data
  end

end
  1. How to pass additional data to the serializer besides the serialization object itself? For example current_user object from Devise
render json: AlbumSerializer.one(album, current_user: current_user) if signed_in?

Thanks!

How can I change the root json path with serialized object inside a hash?

Hi guys, I'm trying to migrate from gem "jsonapi-serializer" to OJ, but I need to do some changes that i'm, not sure the best way to do.

let's check the diference of body:

from jsonapi-serializer

Screenshot 2024-05-16 at 14 39 00

So, with that, I create a new serializer using Og::Serializer, and I camelize with transform_keys :camelize, but I need to add id, attributes and attributes inside a hash, and all this inside a data key

I tried something like this for data

    render json: { data: BorrowerSerializer.many(@list_records) }

Screenshot 2024-05-16 at 14 38 48

And it works, but I still need to improve this serializer to my json have the same attributes that using jsonapi serializer

Any ideias here?

Caching doesn't seem to work correctly

Hey 👋🏼

First off, this is a great gem! I have enjoyed working with it.

I started caching my serializer using the cached macro. As I understand from the documentation, that would use the cache_key on the resource.

It does cache the first time I serialize the resource. Then I updated the resource and serialized it again. The second serialization uses the cached JSON, even though the resource has been updated.

Thanks,
Emil

Deep transform_keys

If I have some nested data (i.e. posts -> user), I don't think there's currently a way to recursively apply transform_keys :camelize without writing a serializer for each sub-component (which can be tedious in more complex data models)

Are there plans to add a version of transform_keys :camelize that applies to every nested key of arbitrary depth?

Associations DSLs don't respect attribute order

The association DSL correctly serializes, but doesn't follow the same ordering conventions of the other attributes DSLs (attributes, serializer_attributes, etc). This results in all association DSLs always appearing at the end of the serialized JSON.

Example

Serializer

# frozen_string_literal: true

class AlbumSerializer < BaseSerializer
  attributes :id

  has_many :songs, serializer: SongSerializer

  attributes :genres
end

Expected

{
  id: 123,
  songs: {...},
  genres: [...]
}

Actual

{
  id: 123,
  genres: [...],
  songs: {...}
}

Happy to take a stab at fixing this if you point me in the right direction. Great library :). Thanks!

attribute options

Our project has records with options fields
when I try use it as

class SomeSerializer < Oj::Serializer
  attribute :options do
    # some code
  end 
end

I get options that I used in one/many

Differentiating Serializer Attributes Based on Controller Action

Hello,

I'm trying to use Oj::Serializer for serializing objects in my Rails application, and I'm facing an issue that I would like some guidance on.

Assume I have a Player model, and I would like to return different serialized attributes based on the controller action. More specifically, for the index action, I want to render only the first_name and last_name attributes, whereas for the show action, I also want to include the associated album object.

Currently, my serializer is defined like this:

class PlayerSerializer < Oj::Serializer
  attributes :first_name, :last_name
  has_one :album, serializer: AlbumSerializer
end

I was thinking of creating separate serializers for each action (i.e., PlayerIndexSerializer and PlayerShowSerializer), but It's really not what I want.

Thank you in advance for your help.

attribute class method doesn't work without a block

I've noticed that if you have the following:

class PostSerializer < Oj::Serializer
  attribute :title
end

You get an error:

       undefined local variable or method `title' for #<PostSerializer:...>
     # (eval):9:in `render_as_hash'
     # /Users/david/.local/share/rtx/installs/ruby/3.0.5/lib/ruby/gems/3.0.0/gems/oj_serializers-2.0.3/lib/oj_serializers/serializer.rb:209:in `one_as_hash'
     # /Users/david/.local/share/rtx/installs/ruby/3.0.5/lib/ruby/gems/3.0.0/gems/oj_serializers-2.0.3/lib/oj_serializers/serializer.rb:169:in `render_as_hash'

This works fine if you have a block or you use attributes:

# works
attribute :title do
 ...
end

# this works too
attributes :title
  • I have AMS compat on, if that makes any difference

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.