Last active
December 21, 2015 01:09
-
-
Save afair/6225435 to your computer and use it in GitHub Desktop.
JSON Messages - From a Philly.rb lightning talk I gave on August 13, 2013.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var talk = { | |
title: "JSON Messaging: From REST and Beyond", | |
by: { name: "Allen Fair", | |
twitter: "@allenfair" }, | |
event: { organization: "Philly.rb", | |
when: "August 12, 2013 23:00 UDT", | |
style: "☇ Lightning" }, | |
href: "https://gist.github.com/afair/6225435" | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Do we need a standard method of JSON Request/Response passing? | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var requestMediums = [ | |
"HTTP+REST+Hypermedia", | |
"AJAX", | |
"JS Frontend (Backbone/Ember/etc.)", | |
"Mobile Apps", | |
"Message Queueing", | |
"node.js", | |
"SOA - Service Oriented Architecture", | |
"IPC & RPC - Interprocess Communication, Remote Procedure Calls", | |
]; | |
var standardRequest = {}; // ????? | |
var standardResponse = {}; // ????? | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// REST - Resource (Record) CRUD over HTTP | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var rest_call = { // HTTP as envisioned at JSON | |
method: "GET", // GET POST PUT/PATCH DELETE | |
uri: "http://example.com/post/1",// Resource identifier | |
uriParams: {search:"terms", page:1}, // For GET, Optional | |
headers: {Accept: "application/json" }, // + cookies | |
postParams: {fieldName:"Value"} // For POST, PUT, PATCH | |
postFiles: {fileName:"Data"} // For POST, PUT, PATCH -> Multipart | |
} | |
// NOTE: Request embedded in HTTP Protocol, Data in POST Body | |
var http_response = { | |
httpStatus: 200, // Look here for success | |
httpMessage: "200 Ok" | |
headers: { "Content-Type": "application/json" }, | |
body: "{post:{id:1, title=\"First Post\"}}" // Just the requested data? | |
}; | |
var result = JSON.parse(http_response.body); | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Recommended Rails JSON Response -- circa 2009 | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var rails = { | |
uri: "http://paydrotalks.com/posts/45-standard-json-response-for-rails-and-jquery/", | |
author: "Peter Bui" | |
response: { | |
status: "ok|redirect|error", | |
to: "http://www.redirect-url.com", | |
html: "<b>Insert html</b>", | |
message: "Insert some message here" | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// JSEND - "How JSON responses from web servers should be formatted" | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var jsend = { | |
uri: "http://labs.omniti.com/labs/jsend", | |
jsendResponse: { | |
status: "success", // success, fail, error (exception) | |
data: {post: { id:1, title: "First Post" } } | |
}, | |
jsendFailResponse: { // Request Error | |
status: "fail", | |
data: { title: "Post not found" } // user-defined failure object | |
} | |
jsendErrorResponse: { // Exception | |
status: "error", | |
message: "Could not connect to the database" | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// ODATA -- the product of a deranged Java Architect | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var odata = { | |
uri: "http://www.odata.org/documentation/json-format", | |
serviceDocument: { | |
d : { EntitySets: ["Products", "Categories", "Suppliers"] } }, | |
collectionResult: { | |
d : { | |
results: [ { | |
__metadata: { | |
uri: "http://services.odata.org/OData/OData.svc/Categories(0)", | |
type: "DataServiceProviderDemo.Category" }, | |
ID: 0, Name: "Food", // <=== REQUESTED RECORD | |
Products: { // Included Association | |
collectionResult: { | |
d : { | |
results: [ { | |
__metadata: { | |
uri: "http://services.odata.org/OData/OData.svc/Categories(0)", | |
type: "DataServiceProviderDemo.Category" }, | |
ID: 0, Name: "Food", // <=== REQUESTED RECORD | |
Products: { | |
__deferred: { | |
uri: "http://services.odata.org/OData/OData.svc/Categories(0)/Products" | |
} , | |
}, | |
} ], | |
__count: 2, | |
__next: "http://services.odata.org/OData/OData.svc$skiptoken=12" | |
} | |
}, | |
entryResult: { | |
d : { | |
results: { | |
__metadata: { | |
uri: "http://services.odata.org/OData/OData.svc/Categories(0)", | |
type: "DataServiceProviderDemo.Category" | |
}, | |
ID: 0, Name: "Food", // <=== REQUESTED RECORD | |
Products: { // Association data not included | |
__deferred: { | |
uri: "http://services.odata.org/OData/OData.svc/Categories(0)/Products" | |
} | |
} | |
} | |
} | |
} | |
} | |
} ] | |
} | |
} | |
}; | |
//------------------------------------------------------------------------------ | |
// HAL/HATEOAS: A uniform interface for REST | |
// "REST APIs must be hypertext-driven" -- Roy Fielding | |
//------------------------------------------------------------------------------ | |
var hal = { | |
name: "Hypertext Application Language", | |
alsoSee: "HATEOAS: Hypermedia as the Engine of Application State", | |
uri: "http://stateless.co/hal_specification.html", | |
mediaType:"application/hal+json", | |
collectionResult: { | |
_links: { | |
self: { href: "/orders" }, // self is current URI | |
next: { href: "/orders?page=2" }, // pagination | |
find: { | |
href: "/orders{?id}", | |
templated: true | |
}, | |
admin: [ | |
{ href: "/admins/2", title: "Fred" }, | |
{ href: "/admins/5", title: "Kate" } | |
] | |
}, | |
currentlyProcessing: 14, // Data? | |
shippedToday: 20, | |
_embedded: { | |
order: [ // Associations? | |
{ | |
_links: { | |
self: { href: "/orders/123" }, | |
basket: { href: "/baskets/98712" }, | |
customer: { href: "/customers/7809" } | |
}, | |
total: 30.00, | |
currency: "USD", | |
status: "shipped" | |
}, | |
{ | |
_links: { | |
self: { href: "/orders/124" }, | |
basket: { href: "/baskets/97213" }, | |
customer: { href: "/customers/12369" } | |
}, | |
total: 20.00, | |
currency: "USD", | |
status: "processing" | |
} | |
] | |
} | |
} | |
}; | |
//------------------------------------------------------------------------------ | |
// Hypermedia API / Collection+JSON / Roar | |
//------------------------------------------------------------------------------ | |
var roar = { | |
about: "Roar gem for Hypermedia API's", | |
uri: "https://github.com/apotonick/roar", | |
basedOn: "http://amundsen.com/media-types/collection/format/" | |
singletonResponse: { | |
article: { | |
title: "Lonestar Beer", | |
id: 4711, | |
links: [ | |
{ rel: "self", href: "http://articles/lonestarbeer" } | |
] | |
} | |
} | |
collectionResponse: { | |
collection: { | |
version : "1.0", | |
href : "http://songs/", | |
items : [ | |
{ href : "http://songs/scarifice", | |
data : [ | |
{name: "title", value: "Scarifice"}, | |
{name: "band", value: "Racer X"} | |
], | |
links : [ | |
{rel: "band", href: "http://bands/racer-x"} | |
] | |
} // , ... | |
] | |
} | |
} | |
}; | |
//------------------------------------------------------------------------------ | |
// Siren: a hypermedia specification for representing entities | |
//------------------------------------------------------------------------------ | |
var siren = { | |
uri: "https://github.com/kevinswiber/siren", | |
contentType: "application/vnd.siren+json", | |
response: { | |
class: [ "order" ], | |
properties: { | |
orderNumber: 42, | |
itemCount: 3, | |
status: "pending" | |
}, | |
entities: [ | |
{ class: [ "items", "collection" ], | |
rel: [ "http://x.io/rels/order-items" ], | |
href: "http://api.x.io/orders/42/items" | |
}, | |
{ | |
class: [ "info", "customer" ], // Class | |
rel: [ "http://x.io/rels/customer" ], // Location | |
properties: { // Attributes | |
customerId:"pj123", | |
name: "Peter Joseph" | |
}, | |
links: [ // Links | |
{ rel: [ "self" ], href: "http://api.x.io/customers/pj123" } | |
] | |
} | |
], | |
actions: [ // Set of HTML-form-like actions to send | |
{ | |
name: "add-item", | |
title: "Add Item", | |
method: "POST", | |
href: "http://api.x.io/orders/42/items", | |
type: "application/x-www-form-urlencoded", | |
fields: [ | |
{ name: "orderNumber", type: "hidden", value: "42" }, | |
{ name: "productCode", type: "text" }, | |
{ name: "quantity", type: "number" } | |
] | |
} | |
], | |
links: [ | |
{ rel: [ "self" ], href: "http://api.x.io/orders/42" }, | |
{ rel: [ "previous" ], href: "http://api.x.io/orders/41" }, | |
{ rel: [ "next" ], href: "http://api.x.io/orders/43" } | |
] | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// JSONAPI.org -- From Steve "Hypermedia" Klabnik and Yehuda "Ember" Katz | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var jsonapi = { | |
uri: "http://jsonapi.org/", | |
idDocument: { // ID-Based Document | |
meta: { // Optional, not defined | |
pagination: {} // Mentioned | |
}, | |
posts: [ | |
{ id:1, | |
title: "First Post", | |
links: { | |
author: "9", | |
comments: [ "5", "12", "17", "20" ] | |
} | |
} | |
], | |
}, | |
urlDocument: { // URL-Based Document | |
meta: { // Optional | |
"client-ids": true, // See Client-Side ID's | |
}, | |
posts: [ | |
{ id:1, | |
title: "First Post", | |
links: { | |
author: "http://example.com/people/1", | |
comments: "http://example.com/comments/5,12,17,20" | |
} | |
} | |
], | |
}, | |
urlTemplate: { | |
links: { | |
"posts.comments": "http://example.com/posts/{posts.id}/comments" | |
}, | |
posts: [ | |
{ id: "1", title: "Rails is Omakase" }, | |
{ id: 2, title: "The Parley Letter" } | |
] | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// JSON-RPC - Stateless light-weight remote procedure call protocol | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var jsonRpc = { | |
url: "http://www.jsonrpc.org/specification" | |
request: { | |
jsonrpc: "2.0", // Version number | |
method: "methodName", // Method name to be invoked | |
params: ['parameter'],// Options Parameter Structure, [] or {} | |
id: "identifier" // Client Identifier (string, integer, NULL) | |
}, // NULL is notification, no response requested | |
response: { | |
jsonrpc: "2.0", // Version number | |
result: "value", // Only for success, can be [] or {} | |
error: { // Only for error | |
code: 123, // Integer code for error | |
message: "error message", | |
data: "stuff" // Primitive or Structure ([] {}) from server | |
}, | |
id: "indentifier" // Same as request id | |
} | |
batchRequest: [ request1, request2 ], | |
batchResponse: [ response1, response2 ] // Unless notification request batch | |
}; | |
// | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// SOAPjr - a hybrid of SOAP and JSON-RPC (SOAP, not WSDL) | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var soapJr = { | |
uri: "https://github.com/gitpan/SOAPjr", | |
request: { // basic request to view the jCard details for a single user | |
HEAD: { | |
service_type: "contacts", | |
action_type: "view", | |
sid : "80e5b8a8b9cbf3a79fe8d624628a0fe5" | |
}, | |
BODY: { | |
username: "jbloggs" | |
} | |
}, | |
response: { | |
HEAD: { | |
result: "1" | |
}, | |
BODY: { | |
email: [ | |
{ type: ["internet","pref"], value: "[email protected]" } | |
], | |
fn: "Joe Bloggs", | |
kind: "individual", | |
n: { | |
"family-name":["Bloggs"], | |
"given-name": ["Joe"], | |
value: "Bloggs;Joe" | |
}, | |
org: [ | |
{ "organization-name": "SOAPjr.org" } | |
] | |
} | |
}, | |
soapJsonRequest: { // Example SOAP Request translated to JSON | |
HTTP: { // HTTP, SMTP, TCP, or JMS/Message Queue | |
url: "POST /InStock HTTP/1.1", | |
Host: "www.example.org", | |
ContentType: "application/soap+xml; charset=utf-8", | |
SOAPAction: "http://www.w3.org/2003/05/soap-envelope" | |
} | |
Envelope: { // Like SMTP Delivery | |
Header: {} | |
Body: { | |
GetStockPrice: { // Method | |
StockName:"IBM" // Parameter | |
} | |
} | |
}, | |
soapJsonResponse: { | |
Envelope: { | |
Body: { | |
GetStockPriceResponse: { | |
GetStockPriceResult: "....." // Requested Data | |
OutputParam: ["value"] //if any returned | |
} | |
} | |
} | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// JSON Patch (JSON & HTTP Patch) & Rocket | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var jsonPatch = { | |
url: "http://tools.ietf.org/html/rfc6902", | |
url2: "http://rocket.github.io" | |
httpRequest: { | |
method: "PATCH", // or SUBSCRIBE for Rocket | |
url: "/my/data", | |
contentType: "application/json-patch+json" | |
body: | |
[ | |
{ op: "add", path: "/a/b/c", value: [ "foo", "bar" ] }, | |
{ op: "remove", path: "/a/b/c" }, | |
{ op: "replace", path: "/a/b/c", value: 42 }, | |
{ op: "move", from: "/a/b/c", path: "/a/b/d" }, | |
{ op: "copy", from: "/a/b/d", path: "/a/b/e" }, | |
{ op: "test", path: "/a/b/c", value: "foo" } | |
] | |
} | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Google JSON Style Guide for Requests and Responses | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var googleJsonGuide = { | |
url: "http://google-styleguide.googlecode.com/svn/trunk/jsoncstyleguide.xml" | |
request: "GET http://google.com?q=json+request+standard", | |
response: { | |
apiVersion: "2.1", | |
id: 1, | |
data: { | |
query: "json request standard", | |
time: "0.1", | |
currentItemCount: 10, | |
itemsPerPage: 10, | |
StartIndex: 11, | |
totalItems: 2700000, | |
nextLink: "http://www.google.com/search?hl=en&q=json+request+stardard&start=20&sa=N", | |
previousLink: "http://www.google.com/search?hl=en&q=json+request+stardard&start=0&sa=N", | |
pagingLinkTemplate:"http://www.google.com/search?hl=en&q=json+request+stardard&start={index}&sa=N", | |
}, | |
items: [ | |
{ title: "Jsend", | |
// ... | |
} // , ... | |
] | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Composite JSON Request/Response Documents | |
// - Use only what you need and what makes sense in the transport protocol | |
// - Map request to protocol (HTTP, RPC, Queue, etc.) | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var jsonRequestResponse = { | |
request: { // ClassName.find(id).method_name(*arguments) | |
meta: { // Maps to HTTP Headers & URL Params | |
apiVersion: "1.0", | |
clientName: "MyClient v1.3", | |
clientId: "sessionId|Address|UUID", | |
requestId: "UUID|etc.", | |
requestType: "synchronous|asynchronous|notification", // Respond, queue, or no response required | |
execution: {at:"timestamp", by:"timestamp", retries:10, priority:1, attempts:0, timeout:10}, | |
authentication: {method:"loging|digest|oauth", key:"x", user:"x", password:"x"}, | |
notification: [{service:"name", uri:"...", message:"..."}], | |
pagination: {page:1, size:10}, // For Collections | |
}, | |
// JSON-RPC like request, mappable to REST | |
// POST http://example.com/post/123/approve | |
uri: "path|class|name", // What | |
id: "identifier", // Which | |
method: "methodName", // HTTP Methods, Soap-Style OO Methods | |
arguments: [], // Arguments to method | |
data: {} // For a data push, not process request | |
}, | |
response: { | |
meta: { | |
apiVersion: "1.0" | |
status: "success|error|exception", | |
httpStatus: 200, | |
httpMessage: "200 Ok", | |
redirectTo: "http://redirected.com", | |
execution: {at:"timestamp", duration:1.2, cost:1.23}, | |
message: "Okay", | |
errors: {fieldId:"Missing"}, | |
pagination: {page:1, size:10, pages:10, records:100}, | |
eTag: "686897696a7c876b7e" // Or any addition HTTP-Like header | |
}, | |
post: { id:1, title:"First Post" }, // Entity | |
comments: [ { postId:1, text:"LOL" } ], // Collection, has-many association, etc. | |
links: { | |
self: "http://example.com/posts/1", | |
all: "http://example.com/posts/", | |
page: "http://example.com/posts/?page={page}", | |
first: "http://example.com/posts/1", | |
previous: null, | |
next: "http://example.com/posts/2", | |
last: "http://example.com/posts/100", | |
// Associations | |
author: "http://example.com/author/1", | |
comments: "http://example.com/posts/1/comments" | |
} | |
} | |
}; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// CREDITS | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
var references = [ | |
"http://stackoverflow.com/questions/12806386/standard-json-api-response-format" | |
]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment