JSON

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。

JSON与JS的区别以及和XML的区别具体请参考百度百科

JSON有两种结构:

第一种:对象

“名称/值”对的集合不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。

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

{"姓名": "张三", "年龄": "18"}

第二种:数组

值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

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

值(value)可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。

[

{

"姓名": "张三",

"年龄":"18"

},

{

"姓名": "里斯",

"年龄":"19"

}

]

通过上面的了解可以看出,JSON存在以下几种数据类型(以Java做类比):

json

java

string

Java中的String

number

Java中的Long或Double

true/false

Java中的Boolean

null

Java中的null

[array]

Java中的List或Object[]

{"key":"value"}

Java中的Map

解析JSON

JSON解析器的基本原理

输入一串JSON字符串,输出一个JSON对象。

步骤

JSON解析的过程主要分以下两步:

第一步:对于输入的一串JSON字符串我们需要将其解析成一组token流。

例如 JSON字符串{"姓名": "张三", "年龄": "18"} 我们需要将它解析成

{、 姓名、 :、 张三、 ,、 年龄、 :、 18、 }

这样一组token流

第二步:根据得到的token流将其解析成对应的JSON对象(JSONObject)或者JSON数组(JSONArray)

下面我们来详细分析下这两个步骤:

获取token流

根据JSON格式的定义,token可以分为以下几种类型

token

含义

NULL

null

NUMBER

数字

STRING

字符串

BOOLEAN

true/false

SEP_COLON

:

SEP_COMMA

,

BEGIN_OBJECT

{

END_OBJECT

}

BEGIN_ARRAY

[

END_ARRAY

]

END_DOCUMENT

表示JSON数据结束

根据以上的JSON类型,我们可以将其封装成enum类型的TokenType

package com.json.demo.tokenizer;

/**

BEGIN_OBJECT({)

END_OBJECT(})

BEGIN_ARRAY([)

END_ARRAY(])

NULL(null)

NUMBER(数字)

STRING(字符串)

BOOLEAN(true/false)

SEP_COLON(:)

SEP_COMMA(,)

END_DOCUMENT(表示JSON文档结束)

*/

public enum TokenType {

BEGIN_OBJECT(1),

END_OBJECT(2),

BEGIN_ARRAY(4),

END_ARRAY(8),

NULL(16),

NUMBER(32),

STRING(64),

BOOLEAN(128),

SEP_COLON(256),

SEP_COMMA(512),

END_DOCUMENT(1024);

private int code; // 每个类型的编号

TokenType(int code) {

this.code = code;

}

public int getTokenCode() {

return code;

}

}

在TokenType中我们为每一种类型都赋一个数字,目的是在Parser做一些优化操作(通过位运算来判断是否是期望出现的类型)

在进行第一步之前JSON串对计算机来说只是一串没有意义的字符而已。第一步的作用就是把这些无意义的字符串变成一个一个的token,上面我们已经为每一种token定义了相应的类型和值。所以计算机能够区分不同的token,并能以token为单位解读JSON数据。

下面我们封装一个token类来存储每一个token对应的值

package com.json.demo.tokenizer;

/**

* 存储对应类型的字面量

*/

public class Token {

private TokenType tokenType;

private String value;

public Token(TokenType tokenType, String value) {

this.tokenType = tokenType;

this.value = value;

}

public TokenType getTokenType() {

return tokenType;

}

public void setTokenType(TokenType tokenType) {

this.tokenType = tokenType;

}

public String getValue() {

return value;

}

public void setValue(String value) {

this.value = value;

}

@Override

public String toString() {

return "Token{" +

"tokenType=" + tokenType +

", value='" + value + '\'' +

'}';

}

}

在解析的过程中我们通过字符流来不断的读取字符,并且需要经常根据相应的字符来判断状态的跳转。所以我们需要自己封装一个ReaderChar类,以便我们更好的操作字符流。

package com.json.demo.tokenizer;

import java.io.IOException;

import java.io.Reader;

public class ReaderChar {

private static final int BUFFER_SIZE = 1024;

private Reader reader;

private char[] buffer;

private int index; // 下标

private int size;

public ReaderChar(Reader reader) {

this.reader = reader;

buffer = new char[BUFFER_SIZE];

}

/**

* 返回 pos 下标处的字符,并返回

* @return

*/

public char peek() {

if (index - 1 >= size) {

return (char) -1;

}

return buffer[Math.max(0, index - 1)];

}

/**

* 返回 pos 下标处的字符,并将 pos + 1,最后返回字符

* @return

* @throws IOException

*/

public char next() throws IOException {

if (!hasMore()) {

return (char) -1;

}

return buffer[index++];

}

/**

* 下标回退

*/

public void back() {

index = Math.max(0, --index);

}

/**

* 判断流是否结束

*/

public boolean hasMore() throws IOException {

if (index < size) {

return true;

}

fillBuffer();

return index < size;

}

/**

* 填充buffer数组

* @throws IOException

*/

void fillBuffer() throws IOException {

int n = reader.read(buffer);

if (n == -1) {

return;

}

index = 0;

size = n;

}

}

另外我们还需要一个TokenList来存储解析出来的token流

package com.json.demo.tokenizer;

import java.util.ArrayList;

import java.util.List;

/**

* 存储词法解析所得的token流

*/

public class TokenList {

private List tokens = new ArrayList();

private int index = 0;

public void add(Token token) {

tokens.add(token);

}

public Token peek() {

return index < tokens.size() ? tokens.get(index) : null;

}

public Token peekPrevious() {

return index - 1 < 0 ? null : tokens.get(index - 2);

}

public Token next() {

return tokens.get(index++);

}

public boolean hasMore() {

return index < tokens.size();

}

@Override

public String toString() {

return "TokenList{" +

"tokens=" + tokens +

'}';

}

}

JSON解析比其他文本解析要简单的地方在于,我们只需要根据下一个字符就可知道接下来它所期望读取的到的内容是什么样的。如果满足期望了,则返回 Token,否则返回错误。

为了方便程序出错时更好的debug,程序中自定义了两个exception类来处理错误信息。(具体实现参考exception包)

下面就是第一步中的重头戏(核心代码):

public TokenList getTokenStream(ReaderChar readerChar) throws IOException {

this.readerChar = readerChar;

tokenList = new TokenList();

// 词法解析,获取token流

tokenizer();

return tokenList;

}

/**

* 将JSON文件解析成token流

* @throws IOException

*/

private void tokenizer() throws IOException {

Token token;

do {

token = start();

tokenList.add(token);

} while (token.getTokenType() != TokenType.END_DOCUMENT);

}

/**

* 解析过程的具体实现方法

* @return

* @throws IOException

* @throws JsonParseException

*/

private Token start() throws IOException, JsonParseException {

char ch;

while (true){ //先读一个字符,若为空白符(ASCII码在[0, 20H]上)则接着读,直到刚读的字符非空白符

if (!readerChar.hasMore()) {

return new Token(TokenType.END_DOCUMENT, null);

}

ch = readerChar.next();

if (!isWhiteSpace(ch)) {

break;

}

}

switch (ch) {

case '{':

return new Token(TokenType.BEGIN_OBJECT, String.valueOf(ch));

case '}':

return new Token(TokenType.END_OBJECT, String.valueOf(ch));

case '[':

return new Token(TokenType.BEGIN_ARRAY, String.valueOf(ch));

case ']':

return new Token(TokenType.END_ARRAY, String.valueOf(ch));

case ',':

return new Token(TokenType.SEP_COMMA, String.valueOf(ch));

case ':':

return new Token(TokenType.SEP_COLON, String.valueOf(ch));

case 'n':

return readNull();

case 't':

case 'f':

return readBoolean();

case '"':

return readString();

case '-':

return readNumber();

}

if (isDigit(ch)) {

return readNumber();

}

throw new JsonParseException("Illegal character");

}

在start方法中,我们将每个处理方法都封装成了单独的函数。主要思想就是通过一个死循环不停的读取字符,然后再根据字符的期待值,执行不同的处理函数。

下面我们详解分析几个处理函数:

private Token readString() throws IOException {

StringBuilder sb = new StringBuilder();

while(true) {

char ch = readerChar.next();

if (ch == '\\') { // 处理转义字符

if (!isEscape()) {

throw new JsonParseException("Invalid escape character");

}

sb.append('\\');

ch = readerChar.peek();

sb.append(ch);

if (ch == 'u') { // 处理 Unicode 编码,形如 \u4e2d。且只支持 \u0000 ~ \uFFFF 范围内的编码

for (int i = 0; i < 4; i++) {

ch = readerChar.next();

if (isHex(ch)) {

sb.append(ch);

} else {

throw new JsonParseException("Invalid character");

}

}

}

} else if (ch == '"') { // 碰到另一个双引号,则认为字符串解析结束,返回 Token

return new Token(TokenType.STRING, sb.toString());

} else if (ch == '\r' || ch == '\n') { // 传入的 JSON 字符串不允许换行

throw new JsonParseException("Invalid character");

} else {

sb.append(ch);

}

}

}

该方法也是通过一个死循环来读取字符,首先判断的是JSON中的转义字符。

JSON中允许出现的有以下几种

\"

\\

\b

\f

\n

\r

\t

\u four-hex-digits

\/

具体的处理方法封装在了isEscape()方法中,处理Unicode 编码时要特别注意一下u的后面会出现四位十六进制数。当读取到一个双引号或者读取到了非法字符('r'或’、'n')循环退出。

判断数字的时候也要特别小心,注意负数,frac,exp等等情况。

通过上面的解析,我们可以得到一组token,接下来我们需要以这组token作为输入,解析出相应的JSON对象

解析出JSON对象

解析之前我们需要定义出JSON对象(JSONObject)和JSON数组(JSONArray)的实体类。

package com.json.demo.jsonstyle;

import com.json.demo.exception.JsonTypeException;

import com.json.demo.util.FormatUtil;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

/**

* JSON的对象形式

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

*/

public class JsonObject {

private Map map = new HashMap();

public void put(String key, Object value) {

map.put(key, value);

}

public Object get(String key) {

return map.get(key);

}

...

}

package com.json.demo.jsonstyle;

import com.json.demo.exception.JsonTypeException;

import com.json.demo.util.FormatUtil;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

/**

* JSON的数组形式

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

*/

public class JsonArray {

private List list = new ArrayList();

public void add(Object obj) {

list.add(obj);

}

public Object get(int index) {

return list.get(index);

}

public int size() {

return list.size();

}

...

}

之后我们就可以写解析类了,由于代码较长,这里就不展示了。有兴趣的可以去GitHub上下载。实现逻辑比较简单,也易于理解。

解析类中的parse方法首先根据第一个token的类型选择调用parseJsonObject()或者parseJsonArray(),进而返回JSON对象或者JSON数组。上面的解析方法中利用位运算来判断字符的期待值既提高了程序的执行效率也有助于提高代码的ke'du'xi

完成之后我们可以写一个测试类来验证下我们的解析器的运行情况。我们可以自己定义一组JSON串也可以通过HttpUtil工具类从网上获取。最后通过FormatUtil类来规范我们输出。

具体效果如下图所示:

参考文章

c语言 json解析器,撸一个JSON解析器相关推荐

  1. json解析对应的value为null_徒手撸一个JSON解析器

      Java大联盟 致力于最高效的Java学习 关注 作者 | 田小波 cnblogs.com/nullllun/p/8358146.html1.背景JSON(JavaScript Object No ...

  2. 获取Json对象中Json数组中的一个Json对象

    比如:获取以下数据的data中的数据 { "message": "successful", "resultCode": "0&qu ...

  3. json c语言 数组转字符串数组中,json和字符串/数组/集合的互相转换の神操作总结...

    一:前端字符串转JSON的4种方式 1,eval方式解析,恐怕这是最早的解析方式了. function strToJson(str){ var json = eval('(' + str + ')') ...

  4. java 解析器_高性能Java解析器实现过程详解

    如果你没有指定数据或语言标准的或开源的Java解析器, 可能经常要用Java实现你自己的数据或语言解析器.或者,可能有很多解析器可选,但是要么太慢,要么太耗内存,或者没有你需要的特定功能.或者开源解析 ...

  5. java中json数据_java中的JSON对象的使用

    申明:没工作之前都没听过JSON,可能是自己太菜了.可能在前台AJAX接触到JSON,这几天要求在纯java的编程中,返回JSON字符串形式. 网上有两种解析JSON对象的jar包:JSON-lib. ...

  6. mysql sql查询json数据类型_SQL中的JSON数据类型

    SQL中的JSON数据类型 概述 MySQL支持原生JSON类型,使用JSON数据类型相较于将JSON格式的字符串存储在String型中的优势有: 存储时会自动验证JSON文本: 可以优化存储格式.存 ...

  7. Json对象与Json字符串的转化、JSON字符串与Java对象的转换

    一.Json对象与Json字符串的转化 1.jQuery插件支持的转换方式: $.parseJSON( jsonstr ); //jQuery.parseJSON(jsonstr),可以将json字符 ...

  8. json java typeof_Json对象与Json字符串的转化、JSON字符串与Java对象的转换

    一.Json对象与Json字符串的转化 1.jQuery插件支持的转换方式: $.parseJSON( jsonstr ); //jQuery.parseJSON(jsonstr),可以将json字符 ...

  9. Python什么是闭包、闭包特征、定义闭包传入一个数求和并输出,定义一个装饰器:打印函数运行花费的时间,定义一个类:要求:包含一个对象属性,且用_(单下划线)命名的定义一个类方法(装饰器)

    1. 闭包.闭包特征及应用 1.1 什么是闭包? 闭包就是外部函数中定义一个内部函数,内部函数引用外部函数中的变量,外部函数的返回值是内部函数; 闭包是由函数及其相关的引用环境组合而成的实体(即:闭包 ...

  10. python装饰器传递参数_Python装饰器高级版—Python类内定义装饰器并传递self参数...

    本文重点:解决了类里面定义的装饰器,在同一个类里面使用的问题,并实现了装饰器的类属性参数传递 目录: 一.基本装饰器 二.在类里定义装饰器,装饰本类内函数 三.类装饰器 正文: 一.基本装饰器 装饰不 ...

最新文章

  1. php 将内容中的图片的域名,php给编辑器中的图片地址添加域名
  2. java 解析 jar_解析java中对jar包进行再次修改
  3. 【转】采购订单行项目检查增强
  4. 报名 | 东南大学周张泉:基于知识图谱的推理技术
  5. 计算机设备管理系统报告,设备管理信息化自查报告
  6. ArcGIS Server Help 之 Geodatabase and ArcSDE 学习笔记
  7. paip.杀不死进程的原因--僵尸进程的解决.txt
  8. 抖音小程序的私域运营
  9. 微信小程序地图点击设置范围并创建区域蒙层
  10. 关于文件变化监听, 你了解多少?
  11. gas费用测试优化:hardhat-gas-reporter
  12. 根据前序遍历和中序遍历的结果重建二叉树
  13. 【洛谷T2695 桶哥的问题——吃桶】
  14. spring boot 2.0 官方文档 (一)
  15. 腾讯游戏天美工作室实习感悟
  16. 电路基础 01电压、电流和功率
  17. 移动设备网络代码,整理成GO结构体
  18. 微软亚研副院长周明离职,将出任创新工场AI工程院首席科学家
  19. 2022 Weex 工程搭建流程
  20. VUEElement综合案例

热门文章

  1. linux 定位 踩内存_应用稳定性优化系列(二),Crash/Tombstone问题分析及定位
  2. 矩阵拼接_numpy 矩阵拼接
  3. 2018初中计算机考试知识点,[2018年最新整理]全国计算机一级考试MS_Office知识点.docx...
  4. odbc中不显示oracle,oracle:odbc无法提供初始化
  5. 基于Apache ShardingSphere打造分布式数据库
  6. 03_安装和配置 kubectl
  7. 小金鱼呀 python-django-初识Form组件(Form类)
  8. sublime text 安装及使用
  9. PHP版本中的VC6,VC9,VC11,TS,NTS区别
  10. poj3254:基础状压dp