Blame | Last modification | View Log | Download | RSS feed
\documentclass[article]{jss}\usepackage[authoryear,round]{natbib}\usepackage{hyperref}\usepackage{graphicx}\usepackage{caption}\usepackage{listings}\usepackage{enumerate}%\usepackage{times}%\usepackage{fullpage}\usepackage{supertabular}% From http://stackoverflow.com/questions/741985/latex-source-code-listing-like-in-professional-books%\usepackage{beramono} % This messes up the regular verbatim mode.\lstset { % A rudimentary config that shows off some features.basicstyle=\ttfamily, % Without beramono, we'd get cmtt, the teletype font.commentstyle=\textit, % cmtt doesn't do italics. It might do slanted text though.tabsize=4 % Or whatever you use in your editor, I suppose.}% These and \author and \PlainAuthor interact with each other.%\usepackage{colortbl}%\usepackage{dbk_table}\usepackage{appendix}\usepackage{fancyvrb}\usepackage{float}\usepackage{tabularx}\def\hyperlabel#1{}\author{Duncan Temple Lang \\University of California at Davis}\Plainauthor{Duncan Temple Lang \\University of California at Davis}\Abstract{The JavaScript Object Notation (JSON) format is becoming widely usedas a means of exchanging data. It is used in Web Services, clientserver applications and to some extent as a means of serializing datafor use between applications. We describe an R package thatfacilitates both importing data from JSON into R and also exporting Robjects as JSON content. The package is quite simple but thearchitecture allows R programmers to customize several aspects of thecomputations.}\title{JavaScript Object Notation (JSON) and \proglang{R}\\The \OmgPackage{RJSONIO} package}\Keywords{Input/Output, application independent data format, JavaScript, Web Services}\Plainkeywords{Input/Output, application independent data format, JavaScript, Web Services}\input{latexMacros}\begin{document}\section{Things To Fix}\begin{enumerate}\item{}Character Encoding\item{}Base64 Encoding\end{enumerate}\section{JavaScript Object Notation}Purpose of JSON and the need to be able to deal with it in R.Common uses.REST, inserting objects in to JavaScript code e.g. HTML documents.ECMAScript and ActionScript for Flash.Comparison with XML.Not a competition, just different purposes and strengths.The \OmgPackage{RJSONIO}package offers two primary operations - transforming JSON content intoR objects and serializing R objects to JSON format. This allows us toimport and export JSON from R. The two functions that do this are\Rfunc{fromJSON} and \Rfunc{toJSON}.\Rfunc{fromJSON} can read JSON contentfrom a file or a general connection, or from a string in memory.The latter is convenient when we obtain the JSON contentfrom some other computation such as a Web request and the contentis already in memory.Character encodingBefore developing the \OmgPackage{RJSONIO} package, we used \Rpackage{rjson}\citep{bib:rjson} whenserializing R objects into JavaScript code within HTML pages. Thisworked very well for small objects but was too slow for large objects.\OmgPackage{RJSONIO} solves some of the speed issues, primarilyby vectorizing the code that generates the content. We developed the\OmgPackage{RJSONIO} package to be a direct substitute for\Rpackage{rjson} so code that used the latter would not need to bechanged to use \OmgPackage{RJSONIO}. So we can think of\OmgPackage{RJSONIO} as a second-generation of\Rpackage{rjson} with a focus on efficiency which was notwarranted when \Rpackage{rjson} was being developed as means toeffect entirely new facilities for \proglang{R}.\OmgPackage{RJSONIO} also changes the approach used to parseJSON content into R. Firstly, it uses a C library -\Project{libjson} \citep{bib:libjson}. This should yield twobenefits. Firstly, it should be faster than pure interepreted Rcode. Secondly, it relies on code that is used developed by others andused in other applications. The benefit of this is that we do nothave to maintain it and we benefit from any updates as they are madein the \Project{libjson} project. Relying on \Project{libjson}means that we also suffer from its deficiencies and bugs and do nothave the flexibility to design things as we want. The hope is that\DSO{libjson} is used in other projects so that bugswill be identified by a larger audience than if we had developed thecode ourselves for use only in R. Unfortunately, this may not be the case.Since we use \DSO{libjson}, itwould appear we have an additional dependency which users mustsatisfy. However, to simplify installation for users, we haveincluded a copy of the \Project{libjson} code. We use that versiononly if we cannot find a version of \DSO{libjson} on the localmachine to which we are installing the source package. This meansthat R users can elect to use newer versions of \DSO{libjson} butdo not have to.While \OmgPackage{RJSONIO} acts as a direct replacementfor \Rpackage{rjson}, it also offers additional features and controls.The remainder of this paper is organized as follows.We start with a brief description of the simple but generalJSON format. In Section~\ref{sec:examples}, weillustrate how to read JSON content into Rin three different contexts: read local JSON files from Kiva.org,parsing results from a request to the Twitter API,and interacting with CouchDB, a simple client-server database.We then discuss how to serialize R objects in JSON format.In section Section~\ref{sec:customizingParser},we discuss how one can customize the parsing of JSON content.We end with some notes about how the package could be extendedand made more general.\section{The JSON Format}The JSON format is quite simple and reflects the basic data structuresin JavaScript and other programming languages.We'll start with scalar values.Logical values are represented by\verb+true+ and \verb+false+.There is no distinction in JSON betweenan integer and a real valued number.Scientific notation, e.g., 123e10 and 123E-5 is supported.Strings are enclosed within pairs of ", i.e. the double quote character.Arrays are ordered collections of values.Each element is separated by a comma (,).Associative arrays have names for each of the elements.These are equivalent to named lists in R although the orderis not guaranteed.Regular arrays are enclosed by \verb+[ ]+ pairs,again with elements being separated by a comma.Associative arrays are enclosed within \verb+{ }+.Each element is given in the form \verb+name: value+.The \verb+name+ term should be enclosed within quotes.Not all JSON parsers insist on this (including \DSO{libjson}and hence \OmgPackage{RJSONIO}), but it is good practiceto ensure these names are quoted.Each element in an associative or regular array can be an arbitraryJSON object. This allows us to nest values so that we can havean array whose elements are arrays, associative arrays and scalar values.The final element of the format is the literal value \verb+null+.It represent the null object in \proglang{JavaScript} and is a special constant object there,useful for comparing the value of a variable to this special state.In some senses, it corresponds to \Snull{} in \proglang{R}.However, it might also map to an empty vector.The format is very simple Note that it does not have support formathematical terms such as infinity, pi, e.Nor does it have the notion of a \texttt{NA}, the missing value in \proglang{R}.Valid JSON requires that the top-level content be eitheran array or an object. This means that simple literal valuessuch as "2" or 'abc' are not valid, but\verb+"[2]"+ and \verb+"{xyz: 'abc'}"+ are valid.How do we map \verb+null+ to a value in \proglang{R}?How do we map empty vectors in \proglang{R} to JSON?JSON is written as plain text. It would appear that we cannotinclude binary content such as an image.There is however a way around this. We can take arbitrarybinary content and convert it to text using base-64 encodingcommonly used to include binary content in email messages.There are several implementation of functionsthat convert to and from base64 encoding in various R packagesinclude \Rpackage{caTools}, \OmgPackage{RCurl}and \Rpackage{readMzXmlData}.While we can easily include binary content in JSON usingbase64 encoding, it is imperative that the consumerof that JSON content be aware that the content is base64and so can decode it appropriately. Unfortunately,JSON doesn't provide a standard or convenient mechanism for identifyingmeta-data about elements of the content.Valid JavaScript and can be evaluated. Security concerns.\section{Examples}\label{sec:examples}\hyperlabel{sec:examples}%\subsection{Reading Non-Rectangular Data}While many data sets come to us as rectangulartables made up of rows and columnscorresponding to observationseach with the same number of variables.This works reasonably well, but is not richenough for many more complex data structures.We may have repeated measurements for differentobservational units and so not the same number ofvariables in each "row".For each observation, we might have hierarchicalstructures such as their address orlocation. We could collapse this into separatevariables at the top-level, but thismight be a different format for different types ofobservational units.So in short, we need a richer format to representraw data before we project it into a rectangularformat or data frame in \proglang{R}An example of a moderately complex data setis the dump of the Kiva database fromKiva.org. Kiva is an non-profit organizationthat connects lenders and borrowers on-lineto provide micro-loans for people in developing countries.They make several details of loans, borrowers and lendersavailable both via a Web Service API and alsovia serializing their database. The provide this serializationin both XML and JSON formats.The data can be downloaded from\url{http://build.kiva.org/},specifically \url{http://s3.kiva.org/snapshots/kiva_ds_json.zip}.We download and extract the files and this producestwo directories, one for lenders and another for loans.Each of these contains a collection of files with the .json extensioneach numbered from 1 to the number of files in that directory.We'll look at the loan files.We can read one of these files with\begin{CodeChunk}\begin{CodeInput}loans1 = fromJSON("loans/1.json")\end{CodeInput}\end{CodeChunk}The result in \Rvar{loans1} is a list with two elements.The first is named "header" and provides information about thecontents of the file, e.g. the number of loans, the date it was serialized.The second element ("loans") contains the data for each loan.Strangely, there are 795 repeated elements which we can identifyby examining the "id" element. This has nothing to do with JSONbut the way the data were dumped from the database.The same occurs in the XML version.So we remove the duplicates with:\begin{CodeChunk}\begin{CodeInput}w = duplicated(sapply(loans1$loans, `[[`, "id"))loans1$loans = loans1$loans[ ! w ]\end{CodeInput}\end{CodeChunk}Now we can look at each loan. We can look at the types of eachelement:\begin{CodeChunk}\begin{CodeInput}table(unlist(lapply(loans1$loans, function(x) sapply(x, class))))character list logical numeric8003 5000 22 5963\end{CodeInput}\end{CodeChunk}So each element has 5 lists, e.g. description, terms, location, borrowers.The location is made up of several fields identifying the town and countryand also latitude and longitude in a separate list named "geo":\begin{CodeChunk}\begin{CodeInput}loans1$loans[[1]]$location$country_code[1] "UG"$country[1] "Uganda"$town[1] "Tororo"$geo$geo$level[1] "town"$geo$pairs[1] "0.75 34.083333"$geo$type[1] "point"\end{CodeInput}\end{CodeChunk}How we chose to represent and work with this data in \proglang{R}depends on what we want to do with it.The lenders data can be read in the same manner. It has a simplerstructure with all but one variable for each lenderbeing a simple scalar. Not all lenders have all variables sowe have a ragged array again. However, we could easily put thisdata into rectangular form by having \texttt{NA} values.Comparison with XML and speed for overall processingor XPath to get sub-elements.XQuery also.\subsection{Web Services}\label{WebServices}\hyperlabel{WebServices}%JSON is commonly used in Web Services,specifically as the result formatin REST (Representational State Transfer) services.The idea is that we make an HTTP request to queryinformation we want.We specify a URL and possibly additional argumentsto parameterize our request.Let's use the \href{https://dev.twitter.com/docs/api}{Twitter API}as an example.Twitter allows us to query the the 20 most recent public "statues" or activitieson Twitter.We send a request to the URL\url{http://api.twitter.com/1/statuses/public_timeline}.We can control the format of the result by appendingone of the strings "xml", "json", "rss" or "atom",separated by a period.\begin{CodeChunk}\begin{CodeInput}url = "http://api.twitter.com/1/statuses/public_timeline"txt = getURLContent(sprintf("%s.json", url))\end{CodeInput}\end{CodeChunk}This returns a string containing the JSON content.This object also has attributes that identifythe content type ("application/json")and the character encoding. These are extracted from theheader of the HTTP response.\footnote{In older versions of \OmgPackage{RCurl}, this was returnedas a binary object. Now, \OmgPackage{RCurl} recognizesthe content type "application/json" as text.}Now that we have the JSON content as a string, we can convert it toR values via \Rfunc{fromJSON}.We do this with\begin{CodeChunk}\begin{CodeInput}tweets = fromJSON(tt, asText = TRUE)\end{CodeInput}\end{CodeChunk}We use the \Sarg{asText} argumentto ensure the function does not confuse thevalue as the name of a file. The functionwill typically guess correctly, but since we knowwe have the JSON content as a string, it isgood practice to indicate this to \Rfunc{fromJSON}.The result is an \proglang{R} list with twenty elements.Each element is also a list with 19 named elements:\begin{CodeChunk}\begin{CodeInput}names(tweets[[1]])[1] "place" "in_reply_to_user_id"[3] "user" "in_reply_to_status_id"[5] "text" "id_str"[7] "favorited" "created_at"[9] "in_reply_to_status_id_str" "geo"[11] "in_reply_to_screen_name" "id"[13] "in_reply_to_user_id_str" "source"[15] "contributors" "coordinates"[17] "retweeted" "retweet_count"[19] "truncated"\end{CodeInput}\end{CodeChunk}The "user" element is also a list:\begin{CodeChunk}\begin{CodeInput}names(tweets[[1]]$user)[1] "statuses_count"[2] "notifications"[3] "profile_text_color"[4] "protected"[5] "default_profile"[6] "profile_sidebar_fill_color"[7] "location"[8] "name"[9] "profile_background_tile"[10] "listed_count"[11] "contributors_enabled"[12] "profile_background_image_url_https"[13] "utc_offset"[14] "url"[15] "id_str"[16] "following"[17] "verified"[18] "favourites_count"[19] "profile_link_color"[20] "profile_image_url_https"[21] "description"[22] "created_at"[23] "profile_sidebar_border_color"[24] "time_zone"[25] "profile_image_url"[26] "is_translator"[27] "default_profile_image"[28] "profile_use_background_image"[29] "id"[30] "show_all_inline_media"[31] "geo_enabled"[32] "friends_count"[33] "profile_background_color"[34] "followers_count"[35] "screen_name"[36] "profile_background_image_url"[37] "follow_request_sent"[38] "lang"\end{CodeInput}\end{CodeChunk}Compelling example: NYTimes? What others?Gloss over the RCurl requests.\subsection{CouchDB}Mention that others have built an R-CouchDB interface.\section{Creating JSON Content from R}To this point, we have seen how we can consume or import JSONcontent in \proglang{R}. We now turn our attention to how wecreate JSON content from \proglang{R} and so export it to other applications.Basically we want to generate text that we store as a stringor write to a connection and which consists of JSON content.Any R programmer can create arbitrary JSON content using R commandssuch as \Rfunc{paste}, \Rfunc{sprintf} and \Rfunc{cat}and character vectors or connections (including \Rfunc{textConnection}).We focus here however on serializing arbitrary R objects in JSON formatso that the information can be restored within another JSON-enabled application.The basic function that takes an R object and serializes it to a JSON stringis \Rfunc{toJSON}.This function takes an R object and serializes its elements.Basically, this maps R vectors (logical, integer, numeric, character)to either a dictionary (or object in JSON terms) or a regular array.If the R vector has a names, we preserve these and use a dictionary.\begin{CodeChunk}\begin{CodeInput}x = c(a = 1, b = 10, c = 20)toJSON(x)\end{CodeInput}\end{CodeChunk}There are occasions when we have names on an R object, butwe want the resulting JSON value to be a simple array.We can use the \Sarg{.withNames} parameter to control this.Passing a value of \SFALSE{} causes the names to be ignored and a regular arrayto be created, e.g.\begin{CodeChunk}\begin{CodeInput}x = c(a = 1, b = 10, c = 20)toJSON(x, .withNames = FALSE)\end{CodeInput}\end{CodeChunk}There are methods for serializing R objects to JSON for variousclasses of R objects. These allow us to customize how some R objectsare translated to JSON.For example, a matrix in R ismerely a one-dimensional vector with a % <r:attr>dim attributethat allows R to treat as a two or more dimensional object. As aresult, by default, it would be serialized as a single long vector in column-wiseorder. However, a matrix might be represented in\proglang{JavaScript} as an array of row arrays, i.e. a top-level container inwhich element is itself a one-dimensional array for a given row.So we define a method to handle \Rclass{matrix} objects in R.It is defined as\begin{CodeChunk}\begin{CodeInput}setMethod("toJSON", "matrix",function(x, container = length(x) > 1 ||length(names(x)) > 0,collapse = "\n", ..., .level = 1L,.withNames = length(x) > 0 && length(names(x)) > 0) {tmp = paste(apply(x, 1, toJSON),collapse = sprintf(",%s", collapse))if(!container)return(tmp)if(.withNames)paste("{", paste(dQuote(names(x)), tmp, sep = ": "), "}")elsepaste("[", tmp, "]")})\end{CodeInput}\end{CodeChunk}With this defined, the code\begin{CodeChunk}\begin{CodeInput}toJSON(matrix(1:10, 5, 2))\end{CodeInput}\end{CodeChunk}yields\begin{verbatim}[ [ 1, 6 ],[ 2, 7 ],[ 3, 8 ],[ 4, 9 ],[ 5, 10 ] ]\end{verbatim}\Rfunc{toJSON} and its methods could be extended to write toa connection. The default connection could be a\Rfunc{textConnection} and if this was not specified(i.e. missing in the initial call), the string rather than theconnection would be returned.This would allow us to avoid collecting the JSON text in memory foran entire object and to emit/flush content to a connection as it was generated.This would save memory and could be important for large objects.\section{Customizing the Parser}\label{sec:customizingParser}\hyperlabel{sec:customizingParser}%We can provide our own handlers to process each elementas it is encountered by the JSON parser.This is similar to the SAX style of parsing for XML.\section{Future Directions}At present, we omit/drop attributes on R objects when serializing toJSON. We use the length, dim and names for vectors, but ignore themfor other types of R objects and ignore any other attributes entirely.To serialize to R Attributes on R objects. We could use either anempty name or .Data for the data part of an object and then"attributes" to identify the list of attributes.We cannot serialize R functions easily to JavaScriptas they do not make a great deal of sense in that language. Instead,we can serialize the source code for a function as a string.This loses information, and this is very important if thefunction has a non-standard environment.We have also experimented with approaches to translating the R syntaxto JavaScript code and possibly R code to JavaScriptin an effort to simplify authoring JavaScript code for use in, e.g.,Web pages.The \DSO{libjson} parser expects the entireJSON content to be in memory when it starts to read,i.e. passed as a single string. We would like to be ableto have the parser read from a file or connectionand access additional bytes of the input stream as it requires them.This would reduce the memory required as we wouldn't have toload the file into memory ahead of time. Instead, we would retaina smaller buffer of content that is being processed.We have developed a bi-directional interface between the JavaScriptinterpreter SpiderMonkey used in the Mozilla/Firefox browser. Thisallows us to pass R objects to JavaScript and vice verse using C-levelreferences. However, we can also transfer objects by value between thetwo languages using \OmgPackage{RJSONIO} with very littleinfrastructure.\clearpage\bibliography{overview}\end{document}