整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

教你手写一个json格式化工具,显示样式自己控制,好

教你手写一个json格式化工具,显示样式自己控制,好看或简洁

SON(JavaScript Object Notation) 是一种轻量级的数据交换格式。在如今的软件编程中越来越重要,不管你是开发web还是app,其数据格式是一定会使用的。

今天我们就手写一款JSON格式化工具。

主要是用html,js和css,简单几行代码就写出一个自己的json格式化工具,显示样式可以自己控制,简洁、方便、好用。而且拿出来还能装逼。

先看一下完成时的效果:这里样式我选择css控制成极简风格的,只要一个格式控制和颜色控制。毕竟写代码的时候不想被视觉污染。


感觉基本的功能都有了,格式化,缩进量,是否显示引号,单引号双引号自动变换,分级展示,控制展开和叠起。觉得功能不全的你还可以继续添加。样式觉得丑的,你可以自己定义css样式。

然后让我们看一下市面上常用的比较成熟的json格式化工具,做下对比。下边这个是google浏览器的插件JSON Edit。

嗯,对比结束,我写的比他的好,嗯,是这样,没毛病

废话少说,上代码:

总共三部分:html、css和js,组合在一起就能用,不过百行代码。如果你嫌麻烦,在文章底部我有一个做好了的打成压缩包分享在github了,链接已有,自己下。

结构如下

html代码:

<!DOCTYPE html>

<html >

<head>

<title>原生json格式化及高亮</title>

<meta name="description" content="json,格式化,高亮">

<meta name="keywords" content="json,格式化,高亮">

<script src="c.js" type="text/javascript"></script>

<link href="s.css" type="text/css" rel="stylesheet"></link>

</head>

<body>

<div class="HeadersRow">

<div id="HeaderTitle">

<h3 id="HeaderSubTitle">JSON格式化及高亮:</h3>

<textarea id="RawJson">

</textarea>

</div>

<div id="ControlsRow">

<input type="Button" value="格式化" onclick="Process()"/>

<span id="TabSizeHolder">

缩进量

<select id="TabSize" onchange="TabSizeChanged()">

<option value="1">1</option>

<option value="2" selected="true">2</option>

<option value="3">3</option>

<option value="4">4</option>

<option value="5">5</option>

<option value="6">6</option>

</select>

</span>

<label for="QuoteKeys">

<input type="checkbox" id="QuoteKeys" onclick="QuoteKeysClicked()" checked="true" />

引号

</label>&nbsp;

<a href="javascript:void(0);" onclick="SelectAllClicked()">全选</a>

&nbsp;

<span id="CollapsibleViewHolder" >

<label for="CollapsibleView">

<input type="checkbox" id="CollapsibleView" onclick="CollapsibleViewClicked()" checked="true" />

显示控制

</label>

</span>

<span id="CollapsibleViewDetail">

<a href="javascript:void(0);" onclick="ExpandAllClicked()">展开</a>

<a href="javascript:void(0);" onclick="CollapseAllClicked()">叠起</a>

<a href="javascript:void(0);" onclick="CollapseLevel(3)">2级</a>

<a href="javascript:void(0);" onclick="CollapseLevel(4)">3级</a>

<a href="javascript:void(0);" onclick="CollapseLevel(5)">4级</a>

<a href="javascript:void(0);" onclick="CollapseLevel(6)">5级</a>

<a href="javascript:void(0);" onclick="CollapseLevel(7)">6级</a>

<a href="javascript:void(0);" onclick="CollapseLevel(8)">7级</a>

<a href="javascript:void(0);" onclick="CollapseLevel(9)">8级</a>

</span>

</div>

<div id="Canvas" class="Canvas"></div>

<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>

<script type="text/javascript" src="m.js"></script>

</body>

</html>

css代码:

css代码是控制样式的,无操作逻辑,你可以换成你自己喜欢的风格,这里不大费篇章的介绍,简单截图带过。看源码的可以下我的分享的压缩包。

js代码:

直接上github链接,我已经上传压缩包,解压可用,源码也可以查看,自己下载看吧。

https://git.oschina.net/lovepeng/share-for-you.git

者 | 无名之辈FTER

责编 | 夕颜

出品 | 程序人生(ID:coder_life)

本文翻译自Rasa官方文档,并融合了自己的理解和项目实战,同时对文档中涉及到的技术点进行了一定程度的扩展,目的是为了更好的理解Rasa工作机制。与本文配套的项目GitHub地址:ChitChatAssistant https://github.com/jiangdongguo/ChitChatAssistant,欢迎star和issues,我们共同讨论、学习!

对话管理

1.1 多轮对话

多轮对话是相对于单轮对话而言的,单轮对话侧重于一问一答,即直接根据用户的问题给出精准的答案。问答更接近一个信息检索的过程,虽然也可能涉及简单的上下文处理,但通常是通过指代消解和 query 补全来完成的,而多轮对话侧重于需要维护一个用户目标状态的表示和一个决策过程来完成任务,具体来说就是用户带着明确的目的而来,希望得到满足特定限制条件的信息或服务,例如:订餐,订票,寻找音乐、电影或某种商品等。因为用户的需求可以比较复杂,可能需要分多轮进行陈述,用户也可能在对话过程中不断修改或完善自己的需求。此外,当用户的陈述的需求不够具体或明确的时候,机器也可以通过询问、澄清或确认来帮助用户找到满意的结果。

因此,任务驱动的多轮对话不是一个简单的自然语言理解加信息检索的过程,而是一个决策过程,需要机器在对话过程中不断根据当前的状态决策下一步应该采取的最优动作(如:提供结果,询问特定限制条件,澄清或确认需求,等等)从而最有效的辅助用户完成信息或服务获取的任务。

注:任务驱动的多轮对话系统通常为封闭域(domain)(闲聊系统为开放域),而特定限制条件对应于槽(Slot),也就是说,用户在满足特定限制条件时就是一次槽值填充的过程,如果用户能够在一次会话中,满足全部的限制条件,那么就不必进行多轮对话,即可直接使用户得到满意的信息或服务。

1.2 对话管理

对话管理,即Dialog Management(DM),它控制着人机对话的过程,是人机对话系统的重要组成部分。DM会根据NLU模块输出的语义表示执行对话状态的更新和追踪,并根据一定策略选择相应的候选动作。简单来说,就是DM会根据对话历史信息,决定此刻对用户的反应,比如在任务驱动的多轮对话系统中,用户带着明确的目的如订餐、订票等,用户需求比较复杂,有很多限制条件,可能需要分多轮进行陈述,一方面,用户在对话过程中可以不断修改或完善自己的需求,另一方面,当用户的陈述的需求不够具体或明确的时候,机器也可以通过询问、澄清或确认来帮助用户找到满意的结果。如下图所示,DM 的输入就是用户输入的语义表达(或者说是用户行为,是 NLU 的输出)和当前对话状态,输出就是下一步的系统行为和更新的对话状态。这是一个循环往复不断流转直至完成任务的过程。

从本质上来说,**任务驱动的对话管理实际就是一个决策过程,系统在对话过程中不断根据当前状态决定下一步应该采取的最优动作(如:提供结果,询问特定限制条件,澄清或确认需求等),从而最有效的辅助用户完成信息或服务获取的任务。**对话管理的任务大致有:

  • 对话状态维护(dialog state tracking, DST)

对话状态是指记录了哪些槽位已经被填充、下一步该做什么、填充什么槽位,还是进行何种操作。用数学形式表达为,t+1 时刻的对话状态S(t+1),依赖于之前时刻 t 的状态St,和之前时刻 t 的系统行为At,以及当前时刻 t+1 对应的用户行为O(t+1)。可以写成S(t+1)←St+At+O(t+1)。

  • 生成系统决策(dialog policy)

根据 DST 中的对话状态(DS),产生系统行为(dialog act),决定下一步做什么 dialog act 可以表示观测到的用户输入(用户输入 -> DA,就是 NLU 的过程),以及系统的反馈行为(DA -> 系统反馈,就是 NLG 的过程)。

  • 作为接口与后端/任务模型进行交互

Rasa Core

Rasa Core是Rasa框架提供的对话管理模块,它类似于聊天机器人的大脑,主要的任务是维护更新对话状态和动作选择,然后对用户的输入作出响应。所谓对话状态是一种机器能够处理的对聊天数据的表征,对话状态中包含所有可能会影响下一步决策的信息,如自然语言理解模块的输出、用户的特征等;所谓动作选择,是指基于当前的对话状态,选择接下来合适的动作,例如向用户追问需补充的信息、执行用户要求的动作等。举一个具体的例子,用户说“帮我妈妈预定一束花”,此时对话状态包括自然语言理解模块的输出、用户的位置、历史行为等特征。在这个状态下,系统接下来的动作可能是:

  • 向用户询问可接受的价格,如“请问预期价位是多少?”;

  • 向用户确认可接受的价格,如“像上次一样买价值200的花可以吗?”

  • 直接为用户预订

2.1 Stories

Rasa的故事是一种训练数据的形式,用来训练Rasa的对话管理模型。故事是用户和人工智能助手之间的对话的表示,转换为特定的格式,其中用户输入表示为相应的意图(和必要的实体),而助手的响应表示为相应的操作名称。Rasa核心对话系统的一个训练示例称为一个故事。这是一个故事数据格式的指南。两段对话样本示例:

<!-- ##表示story的描述,没有实际作用 -->## greet + location/price + cuisine + num people* greet - utter_greet* inform{"location": "rome", "price": "cheap"} - action_on_it - action_ask_cuisine* inform{"cuisine": "spanish"} - action_ask_numpeople * inform{"people": "six"} - action_ack_dosearch
<!-- Form Action-->## happy path * request_weather - weather_form - form{"name": "weather_form"} - form{"name": }

Story格式大致包含三个部分:

  • 1. 用户输入(User Messages)

使用*开头的语句表示用户的输入消息,我们无需使用包含某个具体内容的输入,而是使用NLU管道输出的intent和entities来表示可能的输入。需要注意的是,如果用户的输入可能包含entities,建议将其包括在内,将有助于policies预测下一步action。这部分大致包含三种形式,示例如下:

(1)* greet 表示用户输入没有entity情况;

(2)* inform{"people": "six"} 表示用户输入包含entity情况,响应这类intent为普通action;

(3)* request_weather 表示用户输入Message对应的intent为form action情况;

  • 2. 动作(Actions)

使用-开头的语句表示要执行动作(Action),可分为utterance actions和custom actions,其中,前者在domain.yaml中定义以utter_为前缀,比如名为greet的意图,它的回复应为utter_greet;后者为自定义动作,具体逻辑由我们自己实现,虽然在定义action名称的时候没有限制,但是还是建议以action_为前缀,比如名为inform的意图fetch_profile的意图,它的response可为action_fetch_profile。

  • 3. 事件(Events)

Events也是使用-开头,主要包含槽值设置(SlotSet)和激活/注销表单(Form),它是是Story的一部分,并且必须显示的写出来。Slot Events和Form Events的作用如下:

(1)Slot Events

Slot Events的作用当我们在自定义Action中设置了某个槽值,那么我们就需要在Story中Action执行之后显著的将这个SlotSet事件标注出来,格式为- slot{"slot_name": "value"}。比如,我们在action_fetch_profile中设置了Slot名为account_type的值,代码如下:

from rasa_sdk.actions import Actionfrom rasa_sdk.events import SlotSetimport requests

class FetchProfileAction(Action): def name(self): return "fetch_profile"

def run(self, dispatcher, tracker, domain): url="http://myprofileurl.com" data=requests.get(url).json return [SlotSet("account_type", data["account_type"])]

那么,就需要在Story中执行action_fetch_profile之后,添加- slot{"account_type" : "premium"}。虽然,这么做看起来有点多余,但是Rasa规定这么做必须的,目的是提高训练时准确度。

## fetch_profile* fetch_profile - action_fetch_profile - slot{"account_type" : "premium"} - utter_welcome_premium

当然,如果您的自定义Action中将槽值重置为None,则对应的事件为-slot{"slot_name": }。

(2)Form Events

在Story中主要存在三种形式的表单事件(Form Events),它们可表述为:

  • Form Action事件

Form Action即表单动作事件,是自定义Action的一种,用于一个表单操作。示例如下:

- restaurant_form
  • Form activation事件

form activation即激活表单事件,当form action事件执行后,会立马执行该事件。示例如下:

- form{"name": "restaurant_form"}
  • Form deactivation事件

form deactivation即注销表单事件,作用与form activation相反。示例如下:

- form{"name": }

总之,我们在构建Story时,可以说是多种多样的,因为设计的故事情节是多种多样的,这就意味着上述三种内容的组合也是非常灵活的。另外,在设计Story时Rasa还提供了Checkpoints 和OR statements两种功能,来提升构建Story的灵活度,但是需要注意的是,东西虽好,但是不要太贪了,过多的使用不仅增加了复杂度,同时也会拖慢训练的速度。其中,Checkpoints用于模块化和简化训练数据,示例如下:

## first story* greet - action_ask_user_question> check_asked_question

## user affirms question> check_asked_question* affirm - action_handle_affirmation> check_handled_affirmation

## user denies question> check_asked_question* deny - action_handle_denial> check_handled_denial

## user leaves> check_handled_denial> check_handled_affirmation* goodbye - utter_goodbye

在上面的例子中,可以使用> check_asked_question表示first story,这样在其他story中,如果有相同的first story部分,可以直接用> check_asked_question代替。而OR Statements主要用于实现某一个action可同时响应多个意图的情况,比如下面的例子:

## story* affirm OR thankyou - action_handle_affirmation

2.2 Domain

Domain,译为**“领域”**,它描述了对话机器人应知道的所有信息,类似于“人的大脑”,存储了意图intents、实体entities、插槽slots以及动作actions等信息,其中,intents、entities在NLU训练样本中定义,slots对应于entities类型,只是表现形式不同。domain.yml文件组成结构如下:

具体介绍如下:

1. intents

intents: - affirm - deny - greet - request_weather - request_number - inform - inform_business - stop - chitchat

intents,即意图,是指我们输入一段文本,希望Bot能够明白这段文本是什么意思。在Rasa框架中,意图的定义是在NLU样本中实现的,并且在每个意图下面我们需要枚举尽可多的样本用于训练,以达到Bot能够准确识别出我们输入的一句话到底想要干什么。

2. session_config

session_config: carry_over_slots_to_new_session: true session_expiration_time: 60

session_config,即会话配置,这部分的作用为配置一次会话(conversation session)是否有超时限制。上例演示的是,每次会话的超时时间为60s,如果用户开始一段会话后,在60s内没有输入任何信息,那么这次会话将被结束,然后Bot又会开启一次新的会话,并将上一次会话的Slot值拷贝过来。当然,我们希望舍弃上一次会话Slot的值,可以将carry_over_slots_to_new_session设置为false。另外,当session_expiration_time被设置为0时,Bot永远不会结束当前会话并一直等待用户输入(注:执行action_session_start仍然可以开始一次新的会话,在设置为0的情况下)。

3. slots

slots: date_time: type: unfeaturized auto_fill: false address: type: unfeaturized auto_fill: false

Slots,即插槽,它就像对话机器人的内存,它通过键值对的形式可用来收集存储用户输入的信息(实体)或者查询数据库的数据等。关于Slots的设计与使用,详情请见本文2.6小节。

4. entities

entities: - date_time - address

entities,即实体,类似于输入文本中的关键字,需要在NLU样本中进行标注,然后Bot进行实体识别,并将其填充到Slot槽中,便于后续进行相关的业务操作。

5. actions

actions: - utter_answer_affirm # utter_开头的均为utter actions - utter_answer_deny - utter_answer_greet - utter_answer_goodbye - utter_answer_thanks - utter_answer_whoareyou - utter_answer_whattodo - utter_ask_date_time - utter_ask_address - utter_ask_number - utter_ask_business - utter_ask_type - action_default_fallback # default actions

当Rasa NLU识别到用户输入Message的意图后,Rasa Core对话管理模块就会对其作出回应,而完成这个回应的模块就是action。Rasa Core支持三种action,即default actions、utter actions以及 custom actions。关于如何实现Actions和处理业务逻辑,我们在一篇文章中详谈,这里仅作简单了解。

6. forms

forms: - weather_form

forms,即表单,该部分列举了在NLU样本中定义了哪些Form Actions。关于Form Actions的相关知识,请移步至本文的2.7小节。

7. responses

responses: utter_answer_greet: - text: "您好!请问我可以帮到您吗?" - text: "您好!很高兴为您服务。请说出您要查询的功能?"
utter_ask_date_time: - text: "请问您要查询哪一天的天气?"

utter_ask_address: - text: "请问您要查下哪里的天气?"
utter_default: - text: "没听懂,请换种说法吧~"

responses部分就是描述UtterActions具体的回复内容,并且每个UtterAction下可以定义多条信息,当用户发起一个意图,比如 “你好!”,就触发utter_answer_greet操作,Rasa Core会从该action的模板中自动选择其中的一条信息作为结果反馈给用户。

2.3 Responses

Responses的作用就是自动响应用户输入的信息,因此我们需要管理这些响应(Responses)。Rasa框架提供了三种方式来管理Responses,它们是:

  • 在domain.yaml文件中存储Responses;

  • 在训练数据中存储Responses;

  • 自定义一个NLG服务来生成Responses。

由于第一种我们在本文2.2(7)小节有过介绍,而创建NLG服务是这样的:

nlg: url: http://localhost:5055/nlg # url of the nlg endpoint # you can also specify additional parameters, if you need them: # headers: # my-custom-header: value # token: "my_authentication_token" # will be passed as a get parameter # basic_auth: # username: user # password: pass# example of redis external tracker store configtracker_store: type: redis url: localhost port: 6379 db: 0 password: password record_exp: 30000# example of mongoDB external tracker store config#tracker_store: #type: mongod #url: mongodb://localhost:27017 #db: rasa #user: username #password: password

2.4 Actions

当Rasa NLU识别到用户输入Message的意图后,Rasa Core对话管理模块就会对其作出回应,而完成这个回应的模块就是action。Rasa Core支持三种action,即default actions、utter actions以及 custom actions。关于如何实现Actions和处理业务逻辑,我们在一篇文章中详谈,这里仅作简单了解。

1. default actions

DefaultAction是Rasa Core默认的一组actions,我们无需定义它们,直接可以story和domain中使用。包括以下三种action:

  • action_listen:监听action,Rasa Core在会话过程中通常会自动调用该action;

  • action_restart:重置状态,比初始化Slots(插槽)的值等;

  • action_default_fallback:当Rasa Core得到的置信度低于设置的阈值时,默认执行该action;

2. utter actions

UtterAction是以utter_为开头,仅仅用于向用户发送一条消息作为反馈的一类actions。定义一个UtterAction很简单,只需要在domain.yml文件中的actions:字段定义以utter_为开头的action即可,而具体回复内容将被定义在templates:部分,这个我们下面有专门讲解。定义utter actions示例如下:

actions: - utter_answer_greet - utter_answer_goodbye - utter_answer_thanks - utter_introduce_self - utter_introduce_selfcando - utter_introduce_selffrom

3. custom actions

CustomAction,即自定义action,允许开发者执行任何操作并反馈给用户,比如简单的返回一串字符串,或者控制家电、检查银行账户余额等等。它与DefaultAction不同,自定义action需要我们在domain.yml文件中的actions部分先进行定义,然后在指定的webserver中实现它,其中,这个webserver的url地址在endpoint.yml文件中指定,并且这个webserver可以通过任何语言实现,当然这里首先推荐python来做,毕竟Rasa Core为我们封装好了一个rasa-core-sdk专门用来处理自定义action。关于action web的搭建和action的具体实现,我们在后面详细讲解,这里我们看下在在Rasa Core项目中需要做什么。假如我们在天气资讯的人机对话系统需提供查询天气和空气质量两个业务,那么我们就需要在domain.yml文件中定义查询天气和空气质量的action,即:

actions: ...  - action_search_weather

另外,FormAction也是自定义actions,但是需要在domainl.yaml文件的forms字段声明。

forms: - weather_form

2.5 Policies

Policies是Rasa Core中的策略模块,对应类rasa_core.policies.Policy,它的作用就是使用合适的策略(Policy)来预测一次对话后要执行的行为(Actions)。预测的原理是衡量命中的哪些Policies哪个置信度高,由置信度高的Policy选择合适的Action执行。假如出现不同的Policy拥有相同的置信度,那么就由它们的优先级决定,即选择优先级高的Policy。Rasa对提供的Policies进行了优先级排序,具体如下表:

它们的描述与作用如下:

  • Memoization Policy

MemoizationPolicy只记住(memorizes)训练数据中的对话。如果训练数据中存在这样的对话,那么它将以置信度为1.0预测下一个动作,否则将预测为None,此时置信度为0.0。下面演示了如何在策略配置文件config.yml文件中,配置MemoizationPlicy策略,其中,max_history(超参数)决定了模型查看多少个对话历史以决定下一个执行的action。

 policies: - name: "MemoizationPolicy" max_history: 5

注:max_history值越大训练得到的模型就越大并且训练时间会变长,关于该值到底该设置多少,我们可以举这么个例子,比如有这么一个Intent:out_of_scope来描述用户输入的消息off-topic(离题),当用户连续三次触发out_of_scope意图,这时候我们就需要主动告知用户需要向其提供帮助,如果要Rasa Core能够学习这种模型,max_history应该至少为3。story.md中表现如下:

* out_of_scope - utter_default* out_of_scope - utter_default* out_of_scope - utter_help_message
  • Keras Policy

KerasPolicy策略是Keras框架中实现的神经网络来预测选择执行下一个action,它默认的框架使用LSTM(Long Short-Term Memory,长短期记忆网络)算法,但是我们也可以重写KerasPolicy.model_architecture函数来实现自己的框架(architecture)。KerasPolicy的模型很简单,只是单一的LSTM+Dense+softmax,这就需要我们不断地完善自己的story来把各种情况下的story进行补充。下面演示了如何在策略配置文件config.yml文件中,配置KerasPolicy策略,其中,epochs表示训练的次数,max_history同上。

policies: - name: KerasPolicy epochs: 100 max_history: 5
  • Embedding Policy

基于机器学习的对话管理能够学习复杂的行为以完成任务,但是将其功能扩展到新领域并不简单,尤其是不同策略处理不合作用户行为的能力,以及在学习新任务(如预订酒店)时,如何将完成一项任务(如餐厅预订)重新应用于该任务时的情况。EmbeddingPolicy,即循环嵌入式对话策略(Recurrent Embedding Dialogue Policy,REDP),它通过将actions和对话状态嵌入到相同的向量空间(vector space)能够获得较好的效果,REDP包含一个基于改进的Neural Turing Machine的记忆组件和注意机制,在该任务上显著优于基线LSTM分类器。

EmbeddingPolicy效果上优于KerasPolicy,但是它有个问题是耗时,因为它没有使用GPU、没有充分利用CPU资源。KerasPolicy和EmbeddingPolicy比较示意图如下:

配置EmbeddingPolicy参数:

policies: - name: EmbeddingPolicy epochs: 100 featurizer: - name: FullDialogueTrackerFeaturizer state_featurizer: - name: LabelTokenizerSingleStateFeaturizer

注:新版的Rasa将EmbeddingPolicy重命名为TEDPolicy,但是我在config.yml配置文件中将其替换后,提示无法找到TEDPolicy异常,具体原因不明,暂还未涉及源码分析。

  • Form Policy

FormPolicy是MemoizationPolicy的扩展,用于处理(form)表单的填充事项。当一个FormAction被调用时,FormPolicy将持续预测表单动作,直到表单中的所有槽都被填满,然后再执行对应的FormAction。如果在Bot系统中使用了FormActions,就需要在config.yml配置文件中进行配置。

policies: - name: FormPolicy
  • Mapping Policy

MappingPolicy可用于直接将意图映射到要执行的action,从而实现被映射的action总会被执行,其中,这种映射是通过triggers属性实现的。举个栗子(domain.yml文件中):

intents: - greet: {triggers: utter_goodbye}

其中,greet是意图;utter_goodbye是action。一个意图最多只能映射到一个action,我们的机器人一旦收到映射意图的消息,它将执行对应的action。然后,继续监听下一条message。需要注意的是,对于上述映射,我们还需要要在story.md文件中添加如下样本,否则,任何机器学习策略都可能被预测的action_greet在dialouge历史中突然出现而混淆。

  • Fallback Policy

如果意图识别的置信度低于nlu_threshold,或者没有任何对话策略预测的action置信度高于core_threshold,FallbackPolicy将执行fallback action。通俗来说,就是我们的对话机器人意图识别和action预测的置信度没有满足对应的阈值,该策略将使机器人执行指定的默认action。configs.yml配置如下:

policies: - name: "FallbackPolicy" # 意图理解置信度阈值 nlu_threshold: 0.3 # action预测置信度阈值 core_threshold: 0.3 # fallback action fallback_action_name: 'action_default_fallback'

其中,action_default_fallback是Rasa Core中的一个默认操作,它将向用户发送utter_default模板消息,因此我们需要确保在domain.yml文件中指定此模板。当然,我们也可以在fallback_action_name字段自定义默认回复的action,比如my_fallback_cation,就可以这么改:

policies: - name: "FallbackPolicy" nlu_threshold: 0.4 core_threshold: 0.3 fallback_action_name: "my_fallback_action"

2.6 Slots

Slots,槽值,相当于机器人的内存(memory),它们以键值对的形式存在,用于存储用户输入时消息时比较重要的信息,而这些信息将为Action的执行提供关键数据。Slots的定义位于domain.yaml文件中,它们通常与Entities相对应,即Entities有哪些,Slots就有哪些,并且Slots存储的值就是NLU模型提取的Entities的值。

2.6.1 Slots Type

1. Text类型

示例:

# domain.yamlslots: cuisine: type: text

2. Boolean类型

示例:

slots: is_authenticated: type: bool

3. categorical类型

示例:

slots: risk_level: type: categorical values: - low - medium - high

4. Float类型

示例:

slots: temperature: type: float min_value: -100.0 max_value: 100.0

5. List类型

示例:

slots: shopping_items: type: list

6. Unfeaturized 类型

示例:

slots: internal_user_id: type: unfeaturized

2.6.2 Slots Set

Slots值填充有多种方式,它们的操作方式如下:

1. Slots Initial

# domain.yamlslots: name: type: text initial_value: "human"

在domain.yaml文件中声明slots时,可以通过initial_value字段为当前slot提供一个初始值,也就是说,当会话开始时,被设定初始值的slot已经被填充好。当然,这个操作不是必须的。

2. Slots Set from NLU

# stories.md# story_01* greet{"name": "Ali"} - slot{"name": "Ali"} - utter_greet

假如在stories.md文件添加一个包含-slot{}的story,这就意味着当NLU模型提取到一个名为name的实体且这个实体有在domain.yaml中定义,那么NLU模型提取到的实体值会被自动填充到name槽中。实际上,对于Rasa来说,就算你不添加-slot{}字段,这个实体值也会被提取并自动填充到name槽中。当然,如果你希望禁止这种自动填充行为,改为添加-slot{}字段填充,可以在domain.yaml定义slot时,设置auto_fill的值为False,即:

# domain.yamlslots: name: type: text auto_fill: False

3. Slots Set By Clicking Buttons

# domain.yamlutter_ask_color:- text: "what color would you like?" buttons: - title: "blue"  payload: '/choose{"color": "blue"}' # 格式 '/intent{"entity":"value",...}'  - title: "red" payload: '/choose{"color": "red"}' 

在点击Button时填充Slots的值,是指当我们的Bot(Rasa Core)在回复用户时,可以在回复的消息中附加Button信息,这种Button类似于快捷键,用户获取到之后,可以直接将其发送给Rasa Core,它会直接进行解析以识别intent和提取entity,并将entity的值填充到slot中。比如你让用户通过点击一个按钮来选择一种颜色,那么可以在domain.yaml中utter_ask_color的回复中添加buttons:/choose{"color": "blue"}和/choose{"color": "red"}。注:通常每个button由title和payload字段组成。

4. Slots Set by Actions

from rasa_sdk.actions import Actionfrom rasa_sdk.events import SlotSetimport requests

class FetchProfileAction(Action): def name(self): return "fetch_profile"

def run(self, dispatcher, tracker, domain): url="http://myprofileurl.com" data=requests.get(url).json return [SlotSet("account_type", data["account_type"])]

该示例演示了如何在Custom Action中通过返回事件来填充Slots的值,即调用SlotSet事件函数并将该事件return。需要注意的是,为了达到这个目的,我们在编写Story时必须包含该Slot,即使用-slot{}实现,只有这样Rasa Core就会从提供的信息中进行学习,并决定执行正确的action。Story.md示例如下:

# story_01* greet - action_fetch_profile - slot{"account_type" : "premium"} - utter_welcome_premium

# story_02* greet - action_fetch_profile - slot{"account_type" : "basic"} - utter_welcome_basic

其中,account_type在domain.yaml中定义如下:

slots: account_type: type: categorical values: - premium - basic

2.6.3 Slots Get

目前主要有两种获取Slots值方式:

1. Get Slot in responses

responses: utter_greet: - text: "Hey, {name}. How are you?"

在domain.yaml的responses部分,可以通过{slotname}的形式获取槽值。

2. Get Slot in Custom Action

from rasa_sdk.actions import Actionfrom rasa_sdk.events import SlotSetimport requests

class FetchProfileAction(Action): def name(self): return "fetch_profile"

def run(self, dispatcher, tracker, domain): # 获取slot account_type的值 account_type=tracker.get_slot('account_type') return

Tracker,可理解为跟踪器,作用是以会话会话的形式维护助手和用户之间的对话状态。通过Tracker,能够轻松获取整个对话信息,其中就包括Slot的值。

2.7 Form

在Rasa Core中,当我们执行一个action需要同时填充多个slot时,可以使用FormAction来实现,因为FormAction会遍历监管的所有slot,当发现相关的slot未被填充时,就会向用户主动发起询问,直到所有slot被填充完毕,才会执行接下来的业务逻辑。使用步骤如下:

(1)构造story

在story中,不仅需要考虑用户按照我们的设计准确的提供有效信息,而且还要考虑用户在中间过程改变要执行的意图情况或称输入无效信息,因为对于FormAction来说,如果无法获得预期的信息就会报错,这里我们分别称这两种情况为happy path、unhappy path。示例如下:

## happy path* request_weather - weather_form - form{"name": "weather_form"} 激活form - form{"name": } 使form无效## unhappy path* request_weather - weather_form - form{"name": "weather_form"}* stop - utter_ask_continue* deny - action_deactivate_form - form{"name": }

注:* request_restaurant为意图;- restaurant_form为form action;- form{"name": "restaurant_form"}为激活form;- form{"name": }为注销form;- action_deactivate_form为默认的action,它的作用是用户可能在表单操作过程中改变主意,决定不继续最初的请求,我们使用这个default action来禁止(取消)表单,同时重置要请求的所有slots。

构建stroy最好使用官方提供的Interactive Learning,防止漏掉信息,详细见本文2.8小节。

(2)添加form字段到Domain

在doamin文件下新增forms:部分,并将所有用到的form名称添加到该字段下:

intents: - request_weather
forms: - weather_form

(3)配置FormPolicy

在工程的配置文件configs.yml中,新增FormPolicy策略:

policies: - name: EmbeddingPolicy epochs: 100 max_history: 5 - name: FallbackPolicy fallback_action_name: 'action_default_fallback' - name: MemoizationPolicy max_history: 5 - name: FormPolicy

(4)Form Action实现

class WeatherForm(FormAction):

def name(self) -> Text: """Unique identifier of the form"""

return "weather_form"

@staticmethod def required_slots(tracker: Tracker) -> List[Text]: """A list of required slots that the form has to fill"""

return ["date_time", "address"]

def submit( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[Dict]: """Define what the form has to do after all required slots are filled""" address=tracker.get_slot('address') date_time=tracker.get_slot('date_time')

return

当form action第一被调用时,form就会被激活并进入FormPolicy策略模式。每次执行form action,required_slots会被调用,当发现某个还未被填充时,会主动去调用形式为uter_ask_{slotname}的模板(注:定义在domain.yml的templates字段中);当所有slot被填充完毕,submit方法就会被调用,此时本次form操作完毕被取消激活。

2.8 Interactive Learning

虽然我们可以容易的人工构建story样本数据,但是往往会出现一些考虑不全,甚至出错等问题,基于此,Rasa Core框架为我们提供了一种交互式学习(Interactive Learning)来获得所需的样本数据。在互动学习模式中,当你与机器人交谈时,你会向它提供反馈,这是一种强大的方法来探索您的机器人可以做什么,也是修复它所犯错误的最简单的方法。基于机器学习的对话的一个优点是,当你的机器人还不知道如何做某事时,你可以直接教它。

(1)开启Action Server

python -m rasa run actions --port 5055 --actions actions --debug

(2)开启Interactive Learning

python -m rasa interactive -m models/20200313-101055.tar.gz --endpoints configs/endpoints.yml --config configs/config.yml # 或者(没有已训练模型情况)# rasa会先训练好模型,再开启交互式学习会话python -m rasa interactive --data /data --domain configs/domain.yml --endpoints configs/endpoints.yml --config configs/config.yml 

分别执行(1)、(2)命令后,我们可以预设一个交互场景根据终端的提示操作即可。如果一个交互场景所有流程执行完毕,按Ctrl+C结束并选择Start Fresh进入下一个场景即可。当然Rasa还提供了可视化界面,以帮助你了解每个Story样本构建的过程,网址:http://localhost:5005/visualization.html。

执行流程大致如下:

Bot loaded. Visualisation at http://localhost:5006/visualization.html .Type a message and press enter (press 'Ctr-c' to exit).? Your input -> 查询身份证439912199008071234? Is the intent 'request_idcard' correct for '查询身份证[439912199008071234](id_number)' and are all entities labeled correctly? Yes------Chat History

# Bot You ─────────────────────────────────────────────────────────────────── 1 action_listen─────────────────────────────────────────────────────────────────── 2 查询身份证[439912199008071234](id_number) intent: request_idcard 1.00



Current slots: address: None, business: None, date-time: None, id_number: None, requested_slot: None

------? The bot wants to run 'number_form', correct? YesChat History

# Bot You ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 1 action_listen────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2 查询身份证[439912199008071234](id_number) intent: request_idcard 1.00────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 3 number_form 1.00 您要查询的身份证号码439912199008071234所属人为张三,湖南长沙人,现在就职于地球村物业。 form{"name": "number_form"} slot{"id_number": "439912199008071234"} form{"name": } slot{"requested_slot": }



Current slots: address: None, business: None, date-time: None, id_number: 439912199008071234, requested_slot: None

------? The bot wants to run 'action_listen', correct? Yes

生成的一个Story示例如下:

## interactive_story_10# unhappy path:chitchat stop but continue path* greet - utter_answer_greet* request_number{"type": "身份证号码"} - number_form - form{"name": "number_form"} - slot{"type": "身份证号码"} - slot{"number": } - slot{"business": } - slot{"requested_slot": "number"}* chitchat - utter_chitchat - number_form - slot{"requested_slot": "number"}* stop - utter_ask_continue* affirm - number_form - slot{"requested_slot": "number"}* form: request_number{"number": "440123199087233467"} - form: number_form - slot{"number": "440123199087233467"} - slot{"type": "身份证号码"} - form{"name": } - slot{"requested_slot": }* thanks - utter_noworries

改进ChitChatAssistant项目

3.1 config.yml

# zh_jieba_mitie_embeddings_config.yml

language: "zh"

pipeline:- name: "MitieNLP" model: "data/total_word_feature_extractor_zh.dat"- name: "JiebaTokenizer" dictionary_path: "data/dict"- name: "MitieEntityExtractor"- name: "EntitySynonymMapper"- name: "RegexFeaturizer"- name: "MitieFeaturizer"- name: "EmbeddingIntentClassifier"

policies: - name: FallbackPolicy nlu_threshold: 0.5 ambiguity_threshold: 0.1 core_threshold: 0.5 fallback_action_name: 'action_default_fallback' - name: MemoizationPolicy max_history: 5 - name: FormPolicy - name: MappingPolicy - name: EmbeddingPolicy epochs: 500

考虑到目前项目的样本较少,这里使用MITIE+EmbeddingPolicy组合,虽然训练时慢了点,但是能够保证实体提取的准确性,同时又能够提高意图识别的命中率。

3.2 weather_stories.md

## happy path* request_weather - weather_form - form{"name": "weather_form"} - form{"name": }
## happy path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"} - form{"name": }* thanks - utter_noworries

## unhappy path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* chitchat - utter_chitchat - weather_form - form{"name": }* thanks - utter_noworries

## very unhappy path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* chitchat - utter_chitchat - weather_form* chitchat - utter_chitchat - weather_form* chitchat - utter_chitchat - weather_form - form{"name": }* thanks - utter_noworries

## stop but continue path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* stop - utter_ask_continue* affirm - weather_form - form{"name": }* thanks - utter_noworries

## stop and really stop path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* stop - utter_ask_continue* deny - action_deactivate_form - form{"name": }

## chitchat stop but continue path* request_weather - weather_form - form{"name": "weather_form"}* chitchat - utter_chitchat - weather_form* stop - utter_ask_continue* affirm - weather_form - form{"name": }* thanks - utter_noworries

## stop but continue and chitchat path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* stop - utter_ask_continue* affirm - weather_form* chitchat - utter_chitchat - weather_form - form{"name": }* thanks - utter_noworries

## chitchat stop but continue and chitchat path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* chitchat - utter_chitchat - weather_form* stop - utter_ask_continue* affirm - weather_form* chitchat - utter_chitchat - weather_form - form{"name": }* thanks - utter_noworries

## chitchat, stop and really stop path* greet - utter_answer_greet* request_weather - weather_form - form{"name": "weather_form"}* chitchat - utter_chitchat - weather_form* stop - utter_ask_continue* deny - action_deactivate_form - form{"name": }
## interactive_story_1## 天气 + 时间 + 地点 + 地点* request_weather - weather_form - form{"name": "weather_form"} - slot{"requested_slot": "date_time"}* form: inform{"date_time": "明天"} - form: weather_form - slot{"date_time": "明天"} - slot{"requested_slot": "address"}* form: inform{"address": "广州"} - form: weather_form - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "广州"} - slot{"date_time": "后天"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

## interactive_story_1## 天气 + 时间 + 地点 + 时间* request_weather - weather_form - form{"name": "weather_form"} - slot{"requested_slot": "date_time"}* form: inform{"date_time": "明天"} - form: weather_form - slot{"date_time": "明天"} - slot{"requested_slot": "address"}* form: inform{"address": "广州"} - form: weather_form - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* inform{"address": "上海"} OR request_weather{"address": "深圳"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "广州"} - slot{"address": "上海"} - form{"name": } - slot{"requested_slot": }* affirm - utter_answer_affirm

## interactive_story_2## 天气/时间/地点 + 地点* request_weather{"date_time": "明天", "address": "上海"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "上海"} - form{"name": } - slot{"requested_slot": }* inform{"address": "广州"} OR request_weather{"address": "广州"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "上海"} - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

## interactive_story_3## 天气/时间/地点 + 时间* request_weather{"address": "深圳", "date_time": "后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "深圳"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "大后天"} OR request_weather{"date_time": "大后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "深圳"} - slot{"date_time": "大后天"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

## interactive_story_2## 天气/时间/地点 + 地点 + 时间* request_weather{"date_time": "明天", "address": "上海"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "上海"} - form{"name": } - slot{"requested_slot": }* inform{"address": "北京"} OR request_weather{"address": "北京"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "上海"} - slot{"address": "北京"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "北京"} - slot{"date_time": "后天"} - form{"name": } - slot{"requested_slot": }* affirm - utter_answer_affirm

## interactive_story_3## 天气/时间/地点 + 地点 + 地点* request_weather{"date_time": "后天", "address": "北京"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "北京"} - form{"name": } - slot{"requested_slot": }* inform{"address": "深圳"} OR request_weather{"address": "深圳"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "北京"} - slot{"address": "深圳"} - form{"name": } - slot{"requested_slot": }* inform{"address": "南京"} OR request_weather{"address": "南京"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "深圳"} - slot{"address": "南京"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

## interactive_story_4## 天气/时间/地点 + 时间 + 地点* request_weather{"date_time": "明天", "address": "长沙"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "长沙"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "长沙"} - slot{"date_time": "后天"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "大后天"} OR request_weather{"date_time": "大后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "长沙"} - slot{"date_time": "大后天"} - form{"name": } - slot{"requested_slot": }* affirm - utter_answer_affirm

## interactive_story_5## 天气/时间/地点 + 时间 + 时间* request_weather{"date_time": "后天", "address": "深圳"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "深圳"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "明天"} OR request_weather{"date_time": "明天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "深圳"} - slot{"date_time": "明天"} - form{"name": } - slot{"requested_slot": }* inform{"address": "广州"} OR request_weather{"address": "广州"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "深圳"} - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

## interactive_story_4## 天气/时间 + 地点 + 时间* request_weather{"date_time": "明天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"requested_slot": "address"}* form: inform{"address": "广州"} - form: weather_form - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "明天"} - slot{"address": "广州"} - slot{"date_time": "后天"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

## interactive_story_5## 天气/地点 + 时间 + 时间* request_weather{"address": "广州"} - weather_form - form{"name": "weather_form"} - slot{"address": "广州"} - slot{"requested_slot": "date_time"}* form: inform{"date_time": "后天"} - form: weather_form - slot{"date_time": "后天"} - form{"name": } - slot{"requested_slot": }* inform{"date_time": "明天"} OR request_weather{"date_time": "明天"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "后天"} - slot{"address": "广州"} - slot{"date_time": "明天"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks
## interactive_story_1## 天气/时间/地点 + chit + chit(restart)+询问天气* request_weather{"date_time": "今天", "address": "广州"} - weather_form - form{"name": "weather_form"} - slot{"date_time": "今天"} - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* chitchat - utter_chitchat* chitchat - utter_chitchat - action_restart* request_weather - weather_form - form{"name": "weather_form"} - slot{"requested_slot": "date_time"}* form: inform{"date_time": "今天"} - form: weather_form - slot{"date_time": "今天"} - slot{"requested_slot": "address"}* form: inform{"address": "广州"} - form: weather_form - slot{"address": "广州"} - form{"name": } - slot{"requested_slot": }* thanks - utter_answer_thanks

在构建Story样本时,主要是使用Interactive Learning工具实现,以确保枚举尽可能多的unhappy story,同时又能够防止在构建样本时出现信息遗漏的情况。此外,本版本中除了查询天气这个案例,还新增了其他案例,并列举了如何使用同义词、自定义字典以及正则表达式的使用方法,详细见最新版项目。

GitHub地址:ChitChatAssistant https://github.com/jiangdongguo/ChitChatAssistant,欢迎star和issues,我们共同讨论、学习!

原文链接:

https://blog.csdn.net/AndrExpert/article/details/105434136

?没有监控和日志咋整?老程序员来支招

?朱广权李佳琦直播掉线,1.2亿人在线等

?RPC的超时设置,一不小心就是线上事故!

?拿下Gartner容器产品第一,阿里云打赢云原生关键一战!

?深聊Solidity的测试场景、方法和实践,太详细了,必须收藏!

?万字干货:一步步教你如何在容器上构建持续部署!

?据说,这是当代极客们的【技术风向标】...

今日福利:评论区留言入选,可获得价值299元的「2020 AI开发者万人大会」在线直播门票一张。 快来动动手指,写下你想说的话吧。

者 | 猪哥

责编 | maozz

JSON的诞生原因是因为XML整合到HTML中各个浏览器实现的细节不尽相同,所以道格拉斯·克罗克福特(Douglas Crockford) 和 奇普·莫宁斯达(Chip Morningstar)一起从JS的数据类型中提取了一个子集,作为新的数据交换格式,因为主流的浏览器使用了通用的JavaScript引擎组件,所以在解析这种新数据格式时就不存在兼容性问题,于是他们将这种数据格式命名为 “JavaScript Object Notation”,缩写为 JSON,由此JSON便诞生了!

今天我们来学习一下JSON的结构形式、数据类型、使用场景以及注意事项吧!

JSON格式

上面我们知道JSON是从JavaScript的数据类型中提取出来的子集,那JSON有几种结构形式呢?又有哪些数据类型呢?他们又分别对应着JavaScript中的哪些数据类型呢?

JSON的2种结构形式,键值对形式和数组形式。

举了一个JSON的实例,就是键值对形式的,如下:

{

"person": {

"name": "pig",

"age": "18",

"sex": "man",

"hometown": {

"province": "江西省",

"city": "抚州市",

"county": "崇仁县"

}

}

}

这种结构的JSON数据规则是:一个无序的“‘名称/值’对”集合。一个对象以 {左括号 开始, }右括号 结束。每个“名称”后跟一个 :冒号 ;“‘名称/值’ 对”之间使用 ,逗号 分隔。

因为大多数的时候大家用的JSON可能都是上面那种key-value形式,所以很多人在讲解JSON的时候总是会忽略数组形式,这一点是需要注意的。

那JSON的数组形式是怎么样的呢?举一个实例吧!

["pig", 18, "man", "江西省抚州市崇仁县"]

数组形式的JSON数据就是值(value)的有序集合。一个数组以 [左中括号 开始, ]右中括号 结束。值之间使用 ,逗号 分隔。

JOSN的6种数据类型

上面两种JSON形式内部都是包含value的,那JSON的value到底有哪些类型,而且上期我们说JSON其实就是从Js数据格式中提取了一个子集,那具体有哪几种数据类型呢?

  1. string:字符串,必须要用双引号引起来。

  2. number:数值,与JavaScript的number一致,整数(不使用小数点或指数计数法)最多为 15 位,小数的最大位数是 17。

  3. object:JavaScript的对象形式,{ key:value }表示方式,可嵌套。

  4. array:数组,JavaScript的Array表示方式[ value ],可嵌套。

  5. true/false:布尔类型,JavaScript的boolean类型。

  6. :空值,JavaScript的。

以上数据形式图片来源JSON官方文档:http://www.json.org/json-zh.html

JSON使用场景

介绍完JSON的数据格式,那我们来看看JSON在企业中使用的比较多的场景。

接口返回数据和序列化。JSON用的最多的地方莫过于Web了,现在的数据接口基本上都是返回的JSON,具体细化的场景有:

  1. Ajxa异步访问数据

  2. RPC远程调用

  3. 前后端分离后端返回的数据

  4. 开放API,如百度、高德等一些开放接口

  5. 企业间合作接口

这种API接口一般都会提供一个接口文档,说明接口的入参、出参等,

一般的接口返回数据都会封装成JSON格式,比如类似下面这种

{

"code": 1,

"msg": "success",

"data": {

"name": "pig",

"age": "18",

"sex": "man",

"hometown": {

"province": "江西省",

"city": "抚州市",

"county": "崇仁县"

}

}

}

程序在运行时所有的变量都是保存在内存当中的,如果出现程序重启或者机器宕机的情况,那这些数据就丢失了。一般情况运行时变量并不是那么重要丢了就丢了,但有些内存中的数据是需要保存起来供下次程序或者其他程序使用。

保存内存中的数据要么保存在数据库,要么保存直接到文件中,而将内存中的数据变成可保存或可传输的数据的过程叫做序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

正常的序列化是将编程语言中的对象直接转成可保存或可传输的,这样会保存对象的类型信息,而JSON序列化则不会保留对象类型!

为了让大家更直观的感受区别,猪哥用代码做一个测试,大家一目了然

  1. Python对象直接序列化会保存class信息,下次使用loads加载到内存时直接变成Python对象。

  2. JSON对象序列化只保存属性数据,不保留class信息,下次使用loads加载到内存可以直接转成dict对象,当然也可以转为Person对象,但是需要写辅助方法。

对于JSON序列化不能保存class信息的特点,那JSON序列化还有什么用?答案是当然有用,对于不同编程语言序列化读取有用,比如:我用Python爬取数据然后转成对象,现在我需要将它序列化磁盘,然后使用Java语言读取这份数据,这个时候由于跨语言数据类型不同,所以就需要用到JSON序列化。

存在即合理,两种序列化可根据需求自行选择!

最后就是生成Token和配置文件

首先声明Token的形式多种多样,有JSON、字符串、数字等等,只要能满足需求即可,没有规定用哪种形式。

JSON格式的Token最有代表性的莫过于JWT(JSON Web Tokens)。

随着技术的发展,分布式web应用的普及,通过Session管理用户登录状态成本越来越高,因此慢慢发展成为Token的方式做登录身份校验,然后通过Token去取Redis中的缓存的用户信息,随着之后JWT的出现,校验方式更加简单便捷化,无需通过Redis缓存,而是直接根据Token取出保存的用户信息,以及对Token可用性校验,单点登录更为简单。

使用JWT做过app的登录系统,大概的流程就是:

  1. 用户输入用户名密码

  2. app请求登录中心验证用户名密码

  3. 如果验证通过则生成一个Token,其中Token中包含:

  4. 用户的uid、Token过期时间、过期延期时间等,然后返回给app

  5. app获得Token,保存在cookie中,下次请求其他服务则带上

  6. 其他服务获取到Token之后调用登录中心接口验证

  7. 验证通过则响应

JWT登录认证有哪些优势:

  1. 性能好:服务器不需要保存大量的session

  2. 单点登录(登录一个应用,同一个企业的其他应用都可以访问):使用JWT做一个登录中心基本搞定,很容易实现。

  3. 兼容性好:支持移动设备,支持跨程序调用,Cookie 是不允许垮域访问的,而 Token 则不存在这个问题。

  4. 安全性好:因为有签名,所以JWT可以防止被篡改。更多JWT相关知识自行在网上学习,本文不过多介绍!

说实话JSON作为配置文件使用场景并不多,最具代表性的就是npm的package.json包管理配置文件了,下面就是一个npm的package.json配置文件内容。

{

"name": "server", //项目名称

"version": "0.0.0",

"private": true,

"main": "server.js", //项目入口地址,即执行npm后会执行的项目

"scripts": {

"start": "node ./bin/www" ///scripts指定了运行脚本命令的npm命令行缩写

},

"dependencies": {

"cookie-parser": "~1.4.3", //指定项目开发所需的模块

"debug": "~2.6.9",

"express": "~4.16.0",

"http-errors": "~1.6.2",

"jade": "~1.11.0",

"morgan": "~1.9.0"

}

}

但其实JSON并不合适做配置文件,因为它不能写注释、作为配置文件的可读性差等原因。

配置文件的格式有很多种如:toml、yaml、xml、ini等,目前很多地方开始使用yaml作为配置文件格式。

JSON在Python中的使用

最后我们来看看Python中操作JSON的常用方法有哪些,在Python中操作JSON时需要引入json标准库。

import json

类型转换

Python类型转JSON:json.dump

# 1、Python的dict类型转JSON

person_dict={'name': 'pig', 'age': 18, 'sex': 'man', 'hometown': '江西抚州'}

# indent参数为缩进空格数

person_dict_json=json.dumps(person_dict, indent=4)

print(person_dict_json, '\n')

# 2、Python的列表类型转JSON

person_list=['pig', 18, 'man', '江西抚州']

person_list_json=json.dumps(person_list)

print(person_list_json, '\n')

# 3、Python的对象类型转JSON

person_obj=Person('pig', 18, 'man', '江西抚州')

# 中间的匿名函数是获得对象所有属性的字典形式

person_obj_json=json.dumps(person_obj, default=lambda obj: obj.__dict__, indent=4)

print(person_obj_json, '\n')

执行结果:

JSON转Python类型:json.loads

# 4、JSON转Python的dict类型

person_json='{ "name": "pig","age": 18, "sex": "man", "hometown": "江西抚州"}'

person_json_dict=json.loads(person_json)

print(type(person_json_dict), '\n')

# 5、JSON转Python的列表类型

person_json2='["pig", 18, "man", "江西抚州"]'

person_json_list=json.loads(person_json2)

print(type(person_json_list), '\n')

# 6、JSON转Python的自定义对象类型

person_json='{ "name": "pig","age": 18, "sex": "man", "hometown": "江西抚州"}'

# object_hook参数是将dict对象转成自定义对象

person_json_obj=json.loads(person_json, object_hook=lambda d: Person(d['name'], d['age'], d['sex'], d['hometown']))

print(type(person_json_obj), '\n')

执行结果如下:

对应的数据类型

上面我们演示了Python类型与JSON的相互转换,最开始的时候我们讲过JSON有6种数据类型,那这6种数据类型分别对应Python中的哪些数据类型呢?

需要注意的点

JSON的键名和字符串都必须使用双引号引起来,而Python中单引号也可以表示为字符串,所以这是个比较容易犯的错误!

Python类型与JSON相互转换的时候到底是用load/dump还是用loads\dumps?

  • 他们之间有什么区别?

  • 什么时候该加s什么时候不该加s?

这个我们可以通过查看源码找到答案:

不加s的方法入参多了一个fp表示filepath,最后多了一个写入文件的操作。

所以我们在记忆的时候可以这样记忆:

加s表示转成字符串(str),不加s表示转成文件。

Python自定义对象与JSON相互转换的时候需要辅助方法来指明属性与键名的对应关系,如果不指定一个方法则会抛出异常!

相信有些看的仔细的同学会好奇上面使用json.dumps方法将Python类型转JSON的时候,如果出现中文,则会出现:

\u6c5f\u897f\u629a\u5dde

这种东西,这是为什么呢?

原因是:Python 3中的json在做dumps操作时,会将中文转换成unicode编码,并以16进制方式存储,而并不是UTF-8格式!

总结

今天我们学习了JSON的2种形式,切记JSON还有[...]这种形式的。

学习了JSON的6种数据类型他们分别对于Python中的哪些类型。

了解了JSON的一些使用场景以及实际的例子。

还学习了在Python中如何使用JSON以及需要注意的事项。

一个JSON知识点却分两篇长文(近万字)来讲,其重要性不言而喻。因为不管你是做爬虫、还是做数据分析、web、甚至前端、测试、运维,JSON都是你必须要掌握的一个知识点

本文为作者投稿,版权归作者个人所有。