1.6 R和JSON的傻瓜式编程
问题
如何让R语言的数据类型转换成JSON数据类型?
引言
JSON作为一种轻量级数据格式,被大量地应用在各种程序环境中。JSON(JavaScript Object Notation)是JavaScript的内嵌的标准对象,同时也是MongoDB的表结构存储类型。JSON是半结构化的,可以表达出丰富的文档含义。JSON文档比XML文档要少很多,更适合于网络传输。早期R语言编程很少会用到JSON,但随着R语言的壮大,R也在伸向各种领域,JSON就是与其他领域的一个交点。如何让R语言傻瓜式转型JSON呢?请看下文介绍。
1.6.1 rjson包介绍
rjson是一个R语言与JSON进行转换的包,非常简单,支持R自身语言转型和基于C类库的转型两种方式。rjson包提供的函数只有3个,fromJSON(), newJSONParser(), toJSON()。 下面我们将介绍如何使用rjson包。
本节使用的系统环境是:
- Win7: x86_64-w64-mingw32/x64 (64-bit)
- R: version 3.0.1
注:rjson同时支持Win7环境和Linux环境。
1. 安装并加载rjson
> install.packages("rjson")
> library(rjson)
接下来,我们新建一个JSON文件进行测试。
新建JSON文件: fin0.json
~ vi fin0.json
{
"table1": {
"time": "130911",
"data": {
"code": [
"TF1312",
"TF1403",
"TF1406"
],
"rt_time": [
130911,
130911,
130911
]
}
},
"table2": {
"time": "130911",
"data": {
"contract": [
"TF1312",
"TF1312",
"TF1403"
],
"jtid": [
99,
65,
21
]
}
}
}
2. 调用函数fromJSON(): 从JSON到R
从fin0.json文件中,读取JSON并解析成R语言对象。我们通常把字节或者文字格式转型为程序对象的过程叫反序列化过程,与之相反的过程,叫做序列化过程。
> json_data <- fromJSON(paste(readLines("fin0.json"), collapse=""))
> json_data
$table1
$table1$time
[1] "130911"
$table1$data
$table1$data$code
[1] "TF1312" "TF1403" "TF1406"
$table1$data$rt_time
[1] 130911 130911 130911
$table2
$table2$time
[1] "130911"
$table2$data
$table2$data$contract
[1] "TF1312" "TF1312" "TF1403"
$table2$data$jtid
[1] 99 65 21
检查转型后,对应R语言的数据类型:
> class(json_data)
[1] "list"
> class(json_data$table2)
[1] "list"
> class(json_data$table2$data)
[1] "list"
> class(json_data$table2$data$jtid)
[1] "numeric"
> class(json_data$table1$data$code)
[1] "character"
我们看到原JSON对象转型后,除最内层外,其他都被解析为R的列表(list)类型,最内层则是基本(numeric,character)类型。在R对象结构中取JSON数据的一个叶子节点,JSON的索引路径为 json.table1.data.code[0]。
> json_data$table1$data$code
[1] "TF1312" "TF1403" "TF1406"
> json_data$table1$data$code[1]
[1] "TF1312"
3. toJSON() 从R到JSON
把R对象转型为JSON串,这个过程叫做序列化过程。还以刚才的json_data为例。
> json_str<-toJSON(json_data)
> print(json_str)
[1] "{\"table1\":{\"time\":\"130911\",\"data\":{\"code\":[\"TF1312\",\"TF1403\",\"TF1406\"],\"rt_time\":[130911,130911,130911]}},\"table2\":{\"time\":\"130911\",\"data\":{\"contract\":[\"TF1312\",\"TF1312\",\"TF1403\"],\"jtid\":[99,65,21]}}}"
> cat(json_str)
{"table1":{"time":"130911","data":{"code":["TF1312","TF1403","TF1406"],"rt_time":[130911,130911,130911]}},"table2":{"time":"130911","data":{"contract":["TF1312","TF1312","TF1403"],"jtid":[99,65,21]}}}
我们只要使用toJSON()函数,就可以实现R对象向JSON的转型。如果用print函数输出,结果就是带转义的输出(\");如果直接用cat函数输出,结果就是标准的JSON串格式。把JSON输出到文件fin0_out.json, 有2种方法,即writeLines()和sink()。
> writeLines(json_str, "fin0_out1.json") # writeLines方法
> sink("fin0_out2.json") # sink方法
> cat(json_str)
> sink()
虽然写法不同,但是输出结果是一个样的,writeLines最后新建一个空行。
{"table1":{"time":"130911","data":{"code":["TF1312","TF1403","TF1406"],"rt_time":[130911,130911,130911]}},"table2":{"time":"130911","data":{"contract":["TF1312","TF1312","TF1403"],"jtid":[99,65,21]}}}
4. C语言库和R语言库转型,并进行性能测试
我们对fromJSON进行性能测试
> system.time( y <- fromJSON(json_str,method="C") )
用户 系统 流逝
0 0 0
> system.time( y2 <- fromJSON(json_str,method = "R") )
用户 系统 流逝
0.02 0.00 0.02
> system.time( y3 <- fromJSON(json_str) )
用户 系统 流逝
0 0 0
我们可以看到,基于C语言库的操作比基于R语言库的要快,因为数据量很小,所以0.02并不明显。当JSON串很大的时候,这个差距就会变得相当大了。fromJSON默认使用C语言库的方法,所以我们平时处理不用加method='C'的参数。下面做toJSON的性能测试。
> system.time( y <- toJSON(json_data,method="C") )
用户 系统 流逝
0 0 0
> system.time( y2 <- toJSON(json_data,method = "R") )
用户 系统 流逝
0.02 0.00 0.01
> system.time( y3 <- toJSON(json_data) )
用户 系统 流逝
0 0 0
解释同前。
1.6.2 RJSONIO包介绍
RJSONIO包,提供了2个主要的操作,把JSON串反序列化成R对象,把R对象序列化成JSON串。RJSONIO包的两个主要函数是fromJSON(), toJSON(),它还包括几个辅助函数,即asJSVars(), basicJSONHandler(), Bob(), isValidJSON(), readJSONStream()。RJSONIO包解决了rjson包序列化大对象慢的问题。RJSONIO依赖于底层的C语言类库libjson。
1. 安装并加载RJSONIO
> install.packages("RJSONIO")
> library(RJSONIO)
2. fromJSON() 从JSON到R
同rjson一样,测试fromJSON函数
> json_data <- fromJSON(paste(readLines("fin0.json"), collapse=""))
> json_data
$table1
$table1$time
[1] "130911"
$table1$data
$table1$data$code
[1] "TF1312" "TF1403" "TF1406"
$table1$data$rt_time
[1] 130911 130911 130911
$table2
$table2$time
[1] "130911"
$table2$data
$table2$data$contract
[1] "TF1312" "TF1312" "TF1403"
$table2$data$jtid
[1] 99 65 21
我们发现与 rjson的结果是一样,R对象除最内层外,其他都是列表(list)类型。下面取叶子节点:
> json_data$table1$data$code
[1] "TF1312" "TF1403" "TF1406"
> json_data$table1$data$code[1]
[1] "TF1312"
3. toJSON() 从R到JSON
做toJSON的性能测试:
> json_str<-toJSON(json_data)
> print(json_str)
[1] "{\n \"table1\": {\n \"time\": \"130911\",\n\"data\": {\n \"code\": [ \"TF1312\", \"TF1403\", \"TF1406\" ],\n\"rt_time\": [ 1.3091e+05, 1.3091e+05, 1.3091e+05 ] \n} \n},\n\"table2\": {\n \"time\": \"130911\",\n\"data\": {\n \"contract\": [ \"TF1312\", \"TF1312\", \"TF1403\" ],\n\"jtid\": [ 99, 65, 21 ] \n} \n} \n}"
> cat(json_str)
{
"table1": {
"time": "130911",
"data": {
"code": [ "TF1312", "TF1403", "TF1406" ],
"rt_time": [ 1.3091e+05, 1.3091e+05, 1.3091e+05 ]
}
},
"table2": {
"time": "130911",
"data": {
"contract": [ "TF1312", "TF1312", "TF1403" ],
"jtid": [ 99, 65, 21 ]
}
}
}
toJSON函数输出与rjson是不一样的,这个输出是格式化的。 输出到文件:
> writeLines(json_str, "fin0_io.json")
文件结果:
{
"table1": {
"time": "130911",
"data": {
"code": [ "TF1312", "TF1403", "TF1406" ],
"rt_time": [ 1.3091e+05, 1.3091e+05, 1.3091e+05 ]
}
},
"table2": {
"time": "130911",
"data": {
"contract": [ "TF1312", "TF1312", "TF1403" ],
"jtid": [ 99, 65, 21 ]
}
}
}
4. isValidJSON() 验证JSON是否合法
验证JSON的格式是否合法。
> isValidJSON(json_str)
Error in file(con, "r") : cannot open the connection
> isValidJSON(json_str,TRUE)
[1] TRUE
> isValidJSON(I('{"foo" : "bar"}'))
[1] TRUE
> isValidJSON(I('{foo : "bar"}'))
[1] FALSE
5. asJSVars() 转换为Javascript变量格式
> cat(asJSVars( a = 1:10, myMatrix = matrix(1:15, 3, 5)))
a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ;
myMatrix = [ [ 1, 4, 7, 10, 13 ],
[ 2, 5, 8, 11, 14 ],
[ 3, 6, 9, 12, 15 ] ] ;
得到两个Javascript变量,数组a和二维数组myMatrix.
1.6.3 自定义JSON的实现
在R语言中,我们最常用的类型是data.frame,接下来我们自己实现一下, data.frame类型和JSON的转换。首先,把R的data.frame对象转成我们定义的JSON格式。
定义输出JSON的格式:
[
{
"code": "TF1312",
"rt_time": "152929",
"rt_latest": 93.76,
"rt_bid1": 93.76,
"rt_ask1": 90.76,
"rt_bsize1": 2,
"rt_asize1": 100,
"optionValue": -0.4,
"diffValue": 0.6
}
]
定义R语言data.frame类型对象:
> df<-data.frame(
code=c('TF1312','TF1310','TF1313')
rt_time=c("152929","152929","152929"),
rt_latest=c(93.76,93.76,93.76),
rt_bid1=c(93.76,93.76,93.76),
rt_ask1=c(90.76,90.76,90.76),
rt_bsize1=c(2,3,1),
rt_asize1=c(100,1,11),
optionValue=c(-0.4,0.2,-0.1),
diffValue=c(0.6,0.6,0.5)
)
> df
code rt | time rt | latest rt | bid1 rt | ask1 rt | bsize1 rt | asize1 | optionValue | diffValue |
---|---|---|---|---|---|---|---|---|
1 TF1312 | 152929 | 93.76 | 93.76 | 90.76 | 2 | 100 | -0.4 | 0.6 |
2 TF1310 | 152929 | 93.76 | 93.76 | 90.76 | 3 | 1 | 0.2 | 0.6 |
3 TF1313 | 152929 | 93.76 | 93.76 | 90.76 | 1 | 11 | -0.1 | |
3 TF1313 | 152929 | 93.76 | 93.76 | 90.76 | 11 | 0.1 | 0.5 |
直接使用toJSON,输出的JSON串按列组合成了数组,并不是我们想要的。
> cat(toJSON(df))
{
"code": ["TF1312", "TF1310", "TF1313"],
"rt_time": ["152929", "152929", "152929"],
"rt_latest": [93.76, 93.76, 93.76],
"rt_bid1": [93.76, 93.76, 93.76],
"rt_ask1": [90.76, 90.76, 90.76],
"rt_bsize1": [2, 3, 1],
"rt_asize1": [100, 1, 11],
"optionValue": [-0.4, 0.2, -0.1],
"diffValue": [0.6, 0.6, 0.5]
}
我们要对data.frame类型进行数据处理:
library(plyr)
> cat(toJSON(unname(alply(df, 1, identity))))
[
{
"code": "TF1312",
"rt_time": "152929",
"rt_latest": 93.76,
"rt_bid1": 93.76,
"rt_ask1": 90.76,
"rt_bsize1": 2,
"rt_asize1": 100,
"optionValue": -0.4,
"diffValue": 0.6
},
{
"code": "TF1310",
"rt_time": "152929",
"rt_latest": 93.76,
"rt_bid1": 93.76,
"rt_ask1": 90.76,
"rt_bsize1": 3,
"rt_asize1": 1,
"optionValue": 0.2,
"diffValue": 0.6
},
{
"code": "TF1313",
"rt_time": "152929",
"rt_latest": 93.76,
"rt_bid1": 93.76,
"rt_ask1": 90.76,
"rt_bsize1": 1,
"rt_asize1": 11,
"optionValue": -0.1,
"diffValue": 0.5
}
]
输出的结果已经通过alply函数做了数据变换,这正是我希望的按行输出的结果。
1.6.4 JSON性能比较
性能比较我们做2组测试,一组是rjson和RJSONIO 对大对象进行序列化(toJSON)测试,另一组是RJSONIO包序列化(toJSON) 列式输出和行式输出的测试。
1. rjson和RJSONIO 对大对象进行序列化(toJSON)测试
创建一个rjson测试脚本,在命令行运行。
> library(rjson)
> df<-data.frame(
+ a=rep(letters,10000),
+ b=rnorm(260000),
+ c=as.factor(Sys.Date()-rep(1:260000))
+ )
> system.time(rjson::toJSON(df))
1.01 0.02 1.03
> system.time(rjson::toJSON(df))
1.01 0.03 1.04
> system.time(rjson::toJSON(df))
0.98 0.05 1.03
同样,再创建一个RJSONIO测试脚本,在命令行运行。
> library(RJSONIO)
> df<-data.frame(
+ a=rep(letters,10000),
+ b=rnorm(260000),
+ c=as.factor(Sys.Date()-rep(1:260000))
+ )
> system.time(RJSONIO::toJSON(df))
2.23 0.02 2.24
> system.time(RJSONIO::toJSON(df))
2.30 0.00 2.29
> system.time(RJSONIO::toJSON(df))
2.25 0.01 2.26
对比结果发现,rjson的性能优于RJSONIO。
2. rjson和RJSONIO,序列化(toJSON) 列式输出和行式输出的测试
创建一个rjson测试脚本,在命令行运行。
> library(rjson)
> library(plyr)
> df<-data.frame(
+ a=rep(letters,100),
+ b=rnorm(2600),
+ c=as.factor(Sys.Date()-rep(1:2600))
+ )
> system.time(rjson::toJSON(df))
0.01 0.00 0.02
> system.time(rjson::toJSON(df))
0.01 0.00 0.02
> system.time(rjson::toJSON(unname(alply(df, 1, identity))))
1.55 0.02 1.56
> system.time(rjson::toJSON(unname(alply(df, 1, identity))))
0.83 0.00 0.83
同样,再创建一个RJSONIO测试脚本,在命令行运行。
> library(RJSONIO)
> library(plyr)
> df<-data.frame(
+ a=rep(letters,100),
+ b=rnorm(2600),
+ c=as.factor(Sys.Date()-rep(1:2600))
+ )
> system.time(RJSONIO::toJSON(df))
0.03 0.00 0.03
> system.time(RJSONIO::toJSON(df))
0.04 0.00 0.03
> system.time(RJSONIO::toJSON(unname(alply(df, 1, identity))))
2.82 0.02 2.84
> system.time(RJSONIO::toJSON(unname(alply(df, 1, identity))))
2.06 0.00 2.06
通过测试我们发现,用toJSON直接列式输出,比行式输出要高效得多,这是因为行输出需要多一步数据处理的过程。这里说rjson比RJSONIO高效,而前面说RJSONIO包解决了rjson包序列化大对象慢的问题,似乎有些矛盾。当然,我的一次的测试不能证明这个结论的,也希望大家在有条件、有精力的情况下,自己做一些测试。