左手用R右手Python系列之——json序列化与反序列化

909 阅读8分钟
原文链接: zhuanlan.zhihu.com

json格式数据作为如今越来越流行的数据交换格式,几乎已经成为web端数据交互的标准,主流的数据科学语言R,Python都中都有非常完善的半结构化数据与json数据进行通讯。本篇文章将会通过简单案例介绍R语言与Python中与json数据进行序列化与反序列化的常用函数。

json的数据以键值对形式存在,在R语言中,符合此标准的就是基础数据对象中的list(严格来说,R语言中所有数据对象都可以表示为list,但是可以保存递归结构只有list一种)。

在R语言中,涉及到json数据处理的,主要是list转换为json和json转换为普通的list。前者被称为序列化,后者被称为反序列化。(也可以理解为编码或者解码的过程)虽然R语言中有三个包可以处理json序列化与反序列化过程(rjson、RJSONIO、jsonlite),但是实际应用最多,功能相对完善的,要数最后一个jsonlite包,这里仅以jsonlite包为例进行讲解。

library("jsonlite")
library("magrittr")
library("RCurl")
mylist <- list(
             "name"="Raiders of the Lost Ark",
             "year"=1981,
             "actors"=list(
             "Indiana Jones" = "Harrison Ford",
             "Dr. Ren Belloq"= "Paul Freeman" 
                      ),
             "producers"= c("Frank Marshall", "George Lucas", "Howard Kazanjian"),
             "budget" = 18000000,
            "academy_award_ve"= TRUE
            )

jsonlite包中的toJSON函数负责将R语言中的数据对象(主要是list)进行序列化。序列化之前需要声明一点,llist必须提供命名,因为json需要严格的键值对结构。

toJSON函数有两个需要强调的参数。

第一个是auto_unbox参数,这个参数控制json对象中值(value)在长度为1时,是否强制转换为数组。如果value对象长度唯一,通常不需要数组化,(因为R语言中没有标量,长度为一的字符或者数值都是原子型向量,默认也会被转换为数组【长度为1】)在大多数场合下,需要指定参数auto_unbox为TRUE。

auto_unbox参数默认为FALSE时:

toJSON(mylist)
{"name":["Raiders of the Lost Ark"],"year":[1981],"actors":{"Indiana Jones":["Harrison Ford"],"Dr. Ren Belloq":["Paul Freeman"]},"producers":["Frank Marshall","George Lucas","Howard Kazanjian"],"budget":[18000000],"academy_award_ve":[true]}

auto_unbox参数指定为TRUE时:

toJSON(mylist,auto_unbox = TRUE)
{"name":"Raiders of the Lost Ark","year":1981,"actors":{"Indiana Jones":"Harrison Ford","Dr. Ren Belloq":"Paul Freeman"},"producers":["Frank Marshall","George Lucas","Howard Kazanjian"],"budget":18000000,"academy_award_ve":true}

prettty参数用于控制输出格式是否进行排版美化(换行渲染)

toJSON(mylist,auto_unbox = TRUE,pretty = TRUE)
   {
    "name" : "Raiders of the Lost Ark",
    "year" : 1981,
    "actors" : {
        "Indiana Jones": "Harrison Ford", 
        "Dr. Ren Belloq": "Paul Freeman" 
        },
    "producers": ["Frank Marshall", "George Lucas", "Howard Kazanjian"],
    "budget" : 18000000,
    "academy_award_ve": true
    }

但是pretty参数仅仅是一个美化参数,仅仅为了更好地进行可视化输出,对于json内容并没有任何影响,在使用时(特别是WEB上传json参数时,通常没有必要设置)。

反序列化:

这里的反序列化就是指如何将一组json字符串反序列化为R语言中的list结构,这种需求在网络数据抓取中使用的及其频繁。

第一种情况:当json字符串来自于手动创建的字符串时:

myjson <-  '{"name":"Raiders of the Lost Ark","year":1981,"actors":{"Indiana Jones":"Harrison Ford","Dr. Ren Belloq":"Paul Freeman"},"producers":["Frank Marshall","George Lucas","Howard Kazanjian"],"budget":18000000,"academy_award_ve":true}'
fromJSON(myjson)
$name
[1] "Raiders of the Lost Ark"
$year
[1] 1981
$actors
$actors$`Indiana Jones`
[1] "Harrison Ford"
$actors$`Dr. Ren Belloq`
[1] "Paul Freeman"
$producers
[1] "Frank Marshall"   "George Lucas"     "Howard Kazanjian"

$budget
[1] 18000000

$academy_award_ve
[1] TRUE

因为json字符串中规定使用英文双引号来包裹所有key键名和字符串格式的value值,所有自己手动建立的包含有json字符串向量时,要使用英文单引号进行表示。这样不至于引起R语言中符号逻辑的混乱。

如果非要使用双引号来建立时,则必须在json字符串内部的所有双引号前使用“\”进行转义,否则R语言无法识别。

myjson <-  "{\"name\":\"Raiders of the Lost Ark\",\"year\":1981,\"actors\":{\"Indiana Jones\":\"Harrison Ford\",\"Dr. Ren Belloq\":\"Paul Freeman\"},\"producers\":[\"Frank Marshall\",\"George Lucas\",\"Howard Kazanjian\"],\"budget\":18000000,\"academy_award_ve\":true}"
fromJSON(myjson)
$name
[1] "Raiders of the Lost Ark"
$year
[1] 1981

$actors$actors
$`Indiana Jones`
[1] "Harrison Ford"

$actors$`Dr. Ren Belloq`
[1] "Paul Freeman"

$producers
[1] "Frank Marshall"   "George Lucas"     "Howard Kazanjian"

$budget
[1] 18000000

$academy_award_ve
[1] TRUE

如果看过之前的几篇关于web抓取的文章,你已经好奇为啥web返回的json原始字符串向量里面存在大量的“\”和“\r\n”。

url <- "http://www.r-datacollection.com/materials/ch-3-xml/peanuts.json"
getURL(url)
  "[\r\n   {\r\n      \"name\":\"van Pelt, Lucy\",\r\n      \"sex\":\"female\",\r\n      \"age\":32\r\n   },\r\n   {\r\n      \"name\":\"Peppermint, Patty\",\r\n      \"sex\":\"female\",\r\n      \"age\":null\r\n   },\r\n   {\r\n      \"name\":\"Brown, Charlie\",\r\n      \"sex\":\"male\",\r\n      \"age\":27\r\n   }\r\n]"
getURL(url) %>% cat()

[
   {      "name":"van Pelt, Lucy",      "sex":"female",      "age":32
   },
   {      "name":"Peppermint, Patty",      "sex":"female",      "age":null
   },
   {      "name":"Brown, Charlie",      "sex":"male",      "age":27
   }
]

实际上这里很好解释,从web端返回的json数据内部所有的分隔符都是双引号,而反会的整个json字串整体作为一个长度为1的原子型字符串向量,但是在R语言中,字符串向量默认使用双引号进行分割,这样就导致json内层的双引号与外侧字符串向量的分割符出现冲突,如果不做任何更改,这样的格式是R语言无法识别的。

fromJSON("{"name":"duyu"}")
Error: unexpected symbol in "fromJSON("{"name"

fromJSON("{\"name\":\"duyu\"}")
$name
[1] "duyu"

所以R语言自动给内层的json分隔符【也就是内层的所有双引号全部都加了转义符,至于”\r\n”,那仅仅是一个换行符,用于优化json排版,使用cat函数可以渲染出最终的效果】。这才是在R语言中,json返回值中出现大量反斜杠的原因。

Python:

Python中主要使用json包进行json的序列化与反序列化。

json序列化:

import json
mydict = {
             "name":"Raiders of the Lost Ark",
             "year":1981,
             "actors":{
                      "Indiana Jones" :"Harrison Ford",
                      "Dr. Ren Belloq": "Paul Freeman" 
                      },
         "producers": ["Frank Marshall", "George Lucas", "Howard Kazanjian"],
         "budget" : 18000000,
         "academy_award_ve": True
            }
json.dumps(mydict)
'{"name": "Raiders of the Lost Ark", "year": 1981, "actors": {"Indiana Jones": "Harrison Ford", "Dr. Ren Belloq": "Paul Freeman"}, "producers": ["Frank Marshall", "George Lucas", "Howard Kazanjian"], "budget": 18000000, "academy_award_ve": true}'

序列化的场景主要用在web请求,如果要求参数提交以json格式提交的话,就需要序列化之后进行提交。(仔细观察你会发现json的数据格式与Python中的dict出奇的一致,确实挺像,但是很多细节明显不一样,比如布尔值,py中是True,json中是true)

反序列化同样涉及到自建json字符串。与R语言中情形一样,使用英文单引号作为字符串分隔符,内层的json字符串对象则必须使用双引号作为分割符号。这样不会导致内外层符号混乱。

myjson = '{"name": "Raiders of the Lost Ark", "year": 1981, "actors": {"Indiana Jones": "Harrison Ford", "Dr. Ren Belloq": "Paul Freeman"}, "producers": ["Frank Marshall", "George Lucas", "Howard Kazanjian"], "budget": 18000000, "academy_award_ve": true}'
json.loads(myjson)
{'academy_award_ve': True,  'actors': {'Dr. Ren Belloq': 'Paul Freeman',   'Indiana Jones': 'Harrison Ford'},  'budget': 18000000,  'name': 'Raiders of the Lost Ark',  'producers': ['Frank Marshall', 'George Lucas', 'Howard Kazanjian'],  'year': 1981}

但是你如果再建立字符串时,坚持使用双引号作为分隔符,为了防止符号混乱,同样需要使用右斜杠转义。

myjson = "{\"name\": \"Raiders of the Lost Ark\", \"year\": 1981, \"actors\": {\"Indiana Jones\": \"Harrison Ford\", \"Dr. Ren Belloq\": \"Paul Freeman\"}, \"producers\": [\"Frank Marshall\", \"George Lucas\", \"Howard Kazanjian\"], \"budget\": 18000000, \"academy_award_ve\": true}"
json.loads(myjson)
{'academy_award_ve': True,  'actors': {'Dr. Ren Belloq': 'Paul Freeman',   'Indiana Jones': 'Harrison Ford'},  'budget': 18000000,  'name': 'Raiders of the Lost Ark',  'producers': ['Frank Marshall', 'George Lucas', 'Howard Kazanjian'],  'year': 1981}

json数据通常来源于webd端的数据请求返回值,但是在Python中,返回值的原始向量,并不会出现像R语言中那种里面存在大量反斜杠的情况,原因在于,Python的字符串分割符默认使用英文单引号(R语言中默认使用英文双引号)。而web端返回的json值严格规定使用英文双引号作为分隔符,这样内层是双引号,外层默认是单引号,所以不会引起歧义,不需要使用反斜杠进行转义。

import requests
url = "http://www.r-datacollection.com/materials/ch-3-xml/peanuts.json"requests.get(url).text'[\r\n   {\r\n      "name":"van Pelt, Lucy",\r\n      "sex":"female",\r\n      "age":32\r\n   },\r\n   {\r\n      "name":"Peppermint, Patty",\r\n      "sex":"female",\r\n      "age":null\r\n   },\r\n   {\r\n      "name":"Brown, Charlie",\r\n      "sex":"male",\r\n      "age":27\r\n   }\r\n]'

可以看到返回值仅仅是包含有\r\n这种换行符(用于美化json排版)。

好在requests函数有一个默认的json方法用于直接处理json返回值。

requests.get(url).json()
[{'age': 32, 'name': 'van Pelt, Lucy', 'sex': 'female'},  {'age': None, 'name': 'Peppermint, Patty', 'sex': 'female'},  {'age': 27, 'name': 'Brown, Charlie', 'sex': 'male'}]

以上json方法调用直接回直接将json字符串转换为Python中的内建对象,dict,但是如果使用urllib包请求,可能就需要使用json库中的json.loads()函数进行反序列化了。

json.loads(requests.get(url).text)
[{'age': 32, 'name': 'van Pelt, Lucy', 'sex': 'female'},  {'age': None, 'name': 'Peppermint, Patty', 'sex': 'female'},  {'age': 27, 'name': 'Brown, Charlie', 'sex': 'male'}]

以上便是R语言与Python中内建非结构化数据对象(list、dict)与json之间的转化方法与核心要点及注意事项。这些函数一定要牢记,在遇到非结构化数据处理时经常用到。

在线课程请点击文末原文链接:

Hellobi Live | 9月12日 R语言可视化在商务场景中的应用R
往期案例数据请移步本人GitHub:
github.com/ljtyduyu/Da…