转载自:https://blog.youkuaiyun.com/fanxiaobin577328725/article/details/63777025
一、简介
中文官网:http://www.json.org/json-zh.html
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。JSON 使用 Javascript语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。 目前非常多的动态(PHP,JSP,.NET)编程语言都支持JSON。
JSON的用途:
- 使用基于JavaScript的应用程序,其中包括浏览器扩展和网站
- 使用JSON格式序列化和结构化的数据传输网络连接
- 主要用于服务器和Web应用程序之间的数据传输
- Web服务和API采用JSON格式提供公共数据
- 它可以用来与现代编程语言
- 易于读写JSON(JSON 具有自我描述性,更易理解)
- 轻量级的基于文本的交换格式
- 独立语言
- JSON 是纯文本
- JSON 具有"自我描述性"(人类可读)
- JSON 具有层级结构(值中存在值)
- JSON 可通过 JavaScript 进行解析
- JSON 数据可使用 AJAX 进行传输
- 没有结束标签
- 更短
- 读写的速度更快
- 能够使用内建的 JavaScript eval() 方法进行解析
- 使用数组
- 不使用保留字
对于 AJAX 应用程序来说,JSON 比 XML 更快更易使用:
- 使用 XML
- 读取 XML 文档
- 使用 XML DOM 来循环遍历文档
- 读取值并存储在变量中
- 使用 JSON
- 读取 JSON 字符串
- 用 eval() 处理 JSON 字符串
在官网的最下方是是所有语言对JSON的实现,而我们看到JSON官网上面只包含了那么多的内容,足见JSON的简单,不要被其中画的那些图所吓到。
分享视频教程:http://pan.baidu.com/s/1o7CfiL8
视频中所讲到的google-gson,其在Maven中央仓库的地址为:https://repo.maven.apache.org/maven2/com/google/code/gson/gson/
参考教程:
- 序列化: 将数据结构或对象转换成二进制串的过程
- 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
二、JSON基础知识
2.1 JSON基础
JSON建构于两种结构:
- “名称/值”对的集合(A collection of name/value pairs)在这里我感觉理解为对象(object)比较恰当。
- 值的有序列表(An ordered list of values)通常被理解为数组(array)。
JSON 语法是 JavaScript 对象表示法语法的子集:
- 数据在名称/值对中
- 数据由逗号分隔
- 大括号保存对象
- 中括号保存数组
JSON 文件:
- JSON 文件的文件类型是 ".json"
- JSON 文本的 MIME 类型是 "application/json"
2.2 数据
JSON数据表示为键值对的形式,书写格式是:名称/值。其中“名称”需要用双引号括起来,然后紧跟一个“冒号(英文)”,然后就是“值”。
JSON 值可以是:
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(在中括号中)
- 对象(在大/花括号中)
- null
2.2.1 数字
JSON 数字可以是整型或者浮点型:- { "age":30 }
2.2.2 对象
JSON 对象在大括号({})中书写,而JSON对象是一个无序设置的名称/值对,对象可以包含多个数据对,数据与数据之间以逗号分隔(包括数组):- { "name":"菜鸟教程" , "url":"www.runoob.com" }
2.2.3 数组
数组在中括号中书写,数组可包含多个对象:
- {
- "sites": [
- { "name":"菜鸟教程" , "url":"www.runoob.com" },
- { "name":"google" , "url":"www.google.com" },
- { "name":"微博" , "url":"www.weibo.com" }
- ]
- }
注意,数组索引是从零开始的。
2.2.4 布尔值
JSON 布尔值可以是 true 或者 false:- { "flag":true }
2.2.5 null
JSON 可以设置 null 值:- { "runoob":null }
2.3 JSON与JavaScript
JSON 是 JavaScript 原生格式,这意味着在 JavaScript 中处理 JSON 数据不需要任何特殊的 API 或工具包。
注:由于JavaScript操作JSON的部分,如果有点JavaScript基础理解起来就非常简单,在这里不做过多叙述,具体可以参考上面的菜鸟教程或易百教程。
三、Java语言中对JSON实现的类库
3.1 json-simple
A simple Java toolkit for JSON. You can use json-simple to encode or decode JSON text.
GitHub托管地址:https://github.com/fangyidong/json-simple
POM依赖:
- <dependency>
- <groupId>com.googlecode.json-simple</groupId>
- <artifactId>json-simple</artifactId>
- <version>1.1.1</version>
- </dependency>
3.1.1 JSON和Java实体之间的映射
JSON.simple实体映射从左侧向右侧解码或解析,并映射实体从右侧到左侧编码:| JSON | Java |
|---|---|
| string | java.lang.String |
| number | java.lang.Number |
| true|false | ava.lang.Boolean |
| null | null |
| array | java.util.List |
| object | java.util.Map |
3.1.2 JSON编码
示例:
- public static void main(String[] args) {
- JSONObject obj = new JSONObject();
- obj.put("name", "foo");
- obj.put("num", new Integer(100));
- obj.put("balance", new Double(1000.21));
- obj.put("is_vip", new Boolean(true));
- System.out.print(obj);
- }
3.1.3 JSON解码
JSONObject 是一个java.util.Map,JSONArray是一个java.util.List,所以可以对其进行访问 Map 和List 的标准操作
- public static void main(String[] args) {
- //创建JSON解析器
- JSONParser parser = new JSONParser();
- String s = "JSON字符串...";
- //解析JSON格式的字符串
- Object obj = parser.parse(s);
- //强制类型转换成响应对象
- ....
- }
可参考:http://www.tuicool.com/articles/M7fiua
3.2 google-gson(推荐)
GitHub托管网址: https://github.com/google/gsonUserGuide:https://github.com/google/gson/blob/master/UserGuide.md#TOC-Gson-With-Maven
3.2.1 基本用法
Gson提供了fromJson() 和toJson() 两个直接用于解析和生成的方法,前者实现反序列化,后者实现了序列化。同时每个方法都提供了重载方法,我常用的总共有5个:基本数据类型的生成:
- Gson gson = new Gson();
- String jsonNumber = gson.toJson(100); // 100
- String jsonBoolean = gson.toJson(false); // false
- String jsonString = gson.toJson("String"); //"String"
- Gson gson = new Gson();
- int i = gson.fromJson("100", int.class); //100
- double d = gson.fromJson("\"99.99\"", double.class); //99.99
- boolean b = gson.fromJson("true", boolean.class); // true
- String str = gson.fromJson("String", String.class); // String
POJO类的生成与解析:
POJO类
- public class User {
- public String name;
- public int age;
- public String emailAddress;
- public User(String name,int age) {
- this.name=name;
- this.age=age;
- }
- public String getEmailAddress() {
- return emailAddress;
- }
- public void setEmailAddress(String emailAddress) {
- this.emailAddress = emailAddress;
- }
- }
- Gson gson = new Gson();
- User user = new User("怪盗kidou",24);
- String jsonObject = gson.toJson(user); // {"name":"怪盗kidou","age":24}
- Gson gson = new Gson();
- String jsonString = "{\"name\":\"怪盗kidou\",\"age\":24}";
- User user = gson.fromJson(jsonString, User.class);
3.2.2 属性重命名 @SerializedName 注解
上面POJO的生成与解析可以看出json的名称与该属性的名称是一一对应的,而且具有一定的容错性,例如double字符串小数点最后包含字符,则会截取前面正确的部分进行转换。 我们开发的时候有可能有这样的需求,POJO属性的名称在生成JSON时可以改一个名称,例如:php和js在命名时一般采用下划线风格,而Java中一般采用的驼峰法,这样就造成前后端习惯的差异。我们知道Gson在序列化和反序列化时需要使用反射,说到反射就不得不想到注解,一般各类库都将注解放到annotations包下,打开源码在com.google.gson包下果然有一个annotations,里面有一个SerializedName的注解类,这应该就是我们要找的。
那么对于json中email_address这个属性对应POJO的属性则变成:
- @SerializedName("email_address")
- public String emailAddress;
3.2.3 为POJO字段提供备选属性名
@SerializedName 是针对生成&解析JSON数据的时候,提供了一个另外一个名称,但有这种情况,当我们接收到一个JSON时需要解析成一个POJO,大部分数据的名称都对应,但是有属性的名称是不一样的,而且与重命名的名称也不一样,我们难道还要重写一个吗,当然不需要。
其实@SerializedName 注解提供了两个属性,上面用到了其中一个,别外还有一个属性alternate,接收一个String数组,
- @SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
- public String emailAddress;
当上面的三个属性(email_address、email、emailAddress)都中出现任意一个时均可以得到正确的POJO结果。
注意:
- alternate属性需要2.4版本及其以上
- 当多种情况同时出时,以最后一个出现的值为准
- Gson gson = new Gson();
- String json = "{\"name\":\"怪盗kidou\",\"age\":24,\"emailAddress\":\"ikidou_1@example.com\",\"email\":\"ikidou_2@example.com\",\"email_address\":\"ikidou_3@example.com\"}";
- User user = gson.fromJson(json, User.class);
- System.out.println(user.emailAddress); // ikidou_3@example.com
3.2.4 数组&泛型
我们通过Gson解析字符串数组的json时,一般有两种方式:使用数组,使用List。而List对于增删都是比较方便的,所以实际使用是还是List比较多。
JSON字符串数组:
- ["Android","Java","PHP"]
使用数组比较简单:
- Gson gson = new Gson();
- String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
- String[] strings = gson.fromJson(jsonArray, String[].class);
- Gson gson = new Gson();
- String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
- String[] strings = gson.fromJson(jsonArray, String[].class);
- List<String> stringList = gson.fromJson(jsonArray, new TypeToken<List<String>>() {}.getType());
泛型解析对接口POJO的设计影响:泛型的引入可以减少无关的代码,提高代码的复用性。
大型公司对接口定义其返回的数据分为两类:
- // data 为 object 的情况
- {"code":"0","message":"success","data":{}} //code状态码,message信息,data返回数据
- // data 为 array 的情况
- {"code":"0","message":"success","data":[]}
Gson不支持泛型或不知道Gson支持泛型的同学一定会这么定义POJO:
- public class UserResponse {
- public int code;
- public String message;
- public User data;
- }
- public class Result<T> {
- public int code;
- public String message;
- public T data;
- }
示例:
- //不再重复定义Result类
- Type userType = new TypeToken<Result<User>>(){}.getType();
- Result<User> userResult = gson.fromJson(json,userType);
- User user = userResult.data;
- Type userListType = new TypeToken<Result<List<User>>>(){}.getType();
- Result<List<User>> userListResult = gson.fromJson(json,userListType);
- List<User> users = userListResult.data;
今后补充参考资料————》》》》: 搞定Gson泛型封装
3.2.5 GsonBuilder
一般情况下Gson类提供的 API已经能满足大部分的使用场景,但我们需要更多更特殊、更强大的功能时,这时候就引入一个新的类 GsonBuilder。GsonBuilder从名上也能知道是用于构建Gson实例的一个类,要想改变Gson默认的设置必须使用该类配置Gson。
GsonBuilder用法:
- Gson gson = new GsonBuilder()
- //各种配置,最后直接调用create方法
- .create(); //生成配置好的Gson
- Gson gson = new GsonBuilder()
- .serializeNulls()
- .create();
- Gson gson = new GsonBuilder()
- //序列化null
- .serializeNulls()
- // 设置日期时间格式,另有2个重载方法
- // 在序列化和反序化时均生效
- .setDateFormat("yyyy-MM-dd")
- // 禁此序列化内部类
- .disableInnerClassSerialization()
- //生成不可执行的Json(多了 )]}' 这4个字符)
- .generateNonExecutableJson()
- //禁止转义html标签
- .disableHtmlEscaping()
- //格式化输出
- .setPrettyPrinting()
- .create();
3.2.6 字段过滤
有些字段可能使我们业务逻辑所需要,但是在序列化的过程中可能不需要,这时我们就需要过滤字段。<1> 基于@Expose注解
从字面上来理解是暴露,name用这个注解意思应该是需要用JSON解析或生成的字段,然而我们前面使用 new Gson();来创建完全不用这个注解也可以实现啊?是的,所以该注解在使用new Gson() 时是不会发生作用。毕竟最常用的API要最简单,所以 @Expose注解必须和GsonBuilder配合使用,new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()。
使用方法: 简单说来就是需要导出的字段上加上@Expose 注解,不导出的字段不加,注意是不导出的不加。
@Expose提供了两个属性,默认值为true,在开发时我们可以根据实际情况进行配置
- @Expose(deserialize = true,serialize = true) //序列化和反序列化都都生效
- @Expose(deserialize = true,serialize = false) //反序列化时生效
- @Expose(deserialize = false,serialize = true) //序列化时生效
- @Expose(deserialize = false,serialize = false) // 和不写一样
Gson在对基于版本的字段导出提供了两个注解 @Since 和 @Until,需要和GsonBuilder.setVersion(Double)配合使用。@Since 和 @Until都接收一个Double值。
使用方法:当前版本(GsonBuilder中设置的版本) 大于等于Since的值时该字段导出,小于Until的值时该该字段导出。
注:当一个字段被同时注解时,需两者同时满足条件。
示例:
- class SinceUntilSample {
- @Since(4)
- public String since;
- @Until(5)
- public String until;
- }
- public void sineUtilTest(double version){
- SinceUntilSample sinceUntilSample = new SinceUntilSample();
- sinceUntilSample.since = "since";
- sinceUntilSample.until = "until";
- Gson gson = new GsonBuilder().setVersion(version).create();
- System.out.println(gson.toJson(sinceUntilSample));
- }
- //当version <4时,结果:{"until":"until"}
- //当version >=4 && version <5时,结果:{"since":"since","until":"until"}
- //当version >=5时,结果:{"since":"since"}
通过public、static 、final、private、protected 这些访问修饰符,进行排除。使用GsonBuilder.excludeFieldsWithModifiers构建gson,支持int形的可变参数,排除的访问修饰符的值由java.lang.reflect.Modifier提供,下面的示例排除了privateField 、 finalField 和staticField 三个字段:
- class ModifierSample {
- final String finalField = "final";
- static String staticField = "static";
- public String publicField = "public";
- protected String protectedField = "protected";
- String defaultField = "default";
- private String privateField = "private";
- }
- ModifierSample modifierSample = new ModifierSample();
- Gson gson = new GsonBuilder()
- .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE)
- .create();
- System.out.println(gson.toJson(modifierSample));
- // 结果:{"publicField":"public","protectedField":"protected","defaultField":"default"}
自定义规则,好处是功能强大、灵活,缺点是相比其它3种方法稍麻烦一点,但也仅仅只是想对其它3种稍麻烦一点而已。
基于策略是利用Gson提供的ExclusionStrategy接口,同样需要使用GsonBuilder,相关API 2个,分别是addSerializationExclusionStrategy 和addDeserializationExclusionStrategy 分别针对序列化和反序化时。这里以序列化为例:
- Gson gson = new GsonBuilder()
- .addSerializationExclusionStrategy(new ExclusionStrategy() {
- @Override
- public boolean shouldSkipField(FieldAttributes f) {
- // 这里作判断,决定要不要排除该字段,return true为排除
- if ("finalField".equals(f.getName())) return true; //按字段名排除
- Expose expose = f.getAnnotation(Expose.class);
- if (expose != null && expose.deserialize() == false) return true; //按注解排除
- return false;
- }
- @Override
- public boolean shouldSkipClass(Class<?> clazz) {
- // 直接排除某个类 ,return true为排除
- return (clazz == int.class || clazz == Integer.class);
- }
- })
- .create();
3.2.7 POJO与JSON的字段映射规则
前面介绍重命名时介绍了@SerializedName这个注解的使用,本节的内容与上一次差不多的,但既然叫映射规则那么自然是有规律的。GsonBuilder提供了FieldNamingStrategy接口,以及setFieldNamingPolicy和setFieldNamingStrategy 两个方法。
<1> 默认实现
GsonBuilder.setFieldNamingPolicy 方法与Gson提供的另一个枚举类FieldNamingPolicy配合使用,该枚举类提供了5种实现方式分别为:
| FieldNamingPolicy | 结果(仅输出emailAddress字段) |
|---|---|
| IDENTITY | {"emailAddress":"ikidou@example.com"} |
| LOWER_CASE_WITH_DASHES | {"email-address":"ikidou@example.com"} |
| LOWER_CASE_WITH_UNDERSCORES | {"email_address":"ikidou@example.com"} |
| UPPER_CAMEL_CASE | {"EmailAddress":"ikidou@example.com"} |
| UPPER_CAMEL_CASE_WITH_SPACES | {"Email Address":"ikidou@example.com"} |
GsonBuilder.setFieldNamingStrategy 方法需要与Gson提供的FieldNamingStrategy接口配合使用,用于实现将POJO的字段与JSON的字段相对应。上面的FieldNamingPolicy实际上也实现了FieldNamingStrategy接口,也就是说FieldNamingPolicy也可以使用setFieldNamingStrategy方法。
用法:
- Gson gson = new GsonBuilder()
- .setFieldNamingStrategy(new FieldNamingStrategy() {
- @Override
- public String translateName(Field f) {
- //实现自己的规则
- return null;
- }
- })
- .create();
今后补充参考资料————》》》》 你真的会用Gson吗?Gson使用指南(四)
四、Struts2
Struts的Result类型有很多,我们如果使用JSON作为返回值,则需要配置其type属性为json。一旦某个逻辑视图名(不理解概念可参考:Struts2学习笔记(二))配置了json类型,这将意味着逻辑视图无须指定物理视图资源,因为JSON插件会将该Action序列化后发送给客户端。
简单来说,JSON插件允许在客户端页面的JavaScript中异步调用Action,而且Action不需要使用视图资源来显示该Action里的状态信息,而是由JSON插件负责将Action里的状态信息返回给调用者——通过这种方式,就可以完成Ajax交互。Struts2关于JSON的插件为struts2-json-plugin-*.jar。
我们在Action处理类中还可以使用@JSON注解,该注解包含如下几个属性:
- name:改变JSON对象的属性名字
- serialize:设置是否序列化该属性
- deserialize:设置是否反序列化该属性
- format:设置用于格式化输出、解析日期表单域的格式。例如“yyyy-MM-dd'T'HH:mm:ss”
示例:
注意下面的示例用到了Ajax,需要引入jquery-1.11.1.js,并且Struts2框架已经配置好。
first.jsp
- <%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
- <%@ taglib prefix="s" uri="/struts-tags" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>使用JSON插件</title>
- <script src="${pageContext.request.contextPath}/jquery-1.11.1.js"
- type="text/javascript">
- </script>
- <script type="text/javascript">
- function gotClick()
- {
- $("#show").hide();
- // 指定向JSONExample发送请求,将id为form1的表单所包含的表单控件转换为请求参数
- $.post("JSONExample" , $("#form1").serializeArray() ,
- // 指定回调函数
- function(data , statusText)
- {
- $("#show").height(80)
- .width(240)
- .css("border" , "1px solid black")
- .css("border-radius" , "15px")
- .css("background-color" , "#efef99")
- .css("color" , "#ff0000")
- .css("padding" , "20px")
- .empty();
- // 遍历JavaScript对象的各属性
- for(var propName in data)
- {
- $("#show").append(propName + "-->"
- + data[propName] + "<br />");
- }
- $("#show").show(600);
- },
- // 指定服务器响应为JSON数据
- "json");
- }
- </script>
- </head>
- <body>
- <s:form id="form1">
- <s:textfield name="field1" label="Field 1"/>
- <s:textfield name="field2" label="Field 2"/>
- <s:textfield name="field3" label="Field 3"/>
- <tr><td colspan="2">
- <input type="button" value="提交" onclick="gotClick();"/>
- </td></tr>
- </s:form>
- <div id="show">
- </div>
- </body>
- </html>
- <action name="JSONExample" class="org.crazyit.app.action.JSONExample">
- <!-- 配置类型的json的Result -->
- <result type="json">
- <!-- 为该Result指定参数 -->
- <param name="noCache">true</param>
- <param name="contentType">text/html</param>
- <!-- 设置只序列Action的map属性 -->
- <!-- param name="root">map</param -->
- </result>
- </action>
- public class JSONExample {
- // 模拟处理结果的成员变量
- private int[] ints = { 10, 20 };
- private Map<String, String> map = new HashMap<String, String>();
- private String customName = "顾客";
- // 封装请求参数的三个成员变量
- private String field1;
- // 'transient'修饰的成员变量不会被序列化
- private transient String field2;
- // 没有setter和getter方法的成员变量不会被序列化
- private String field3;
- public String execute() {
- map.put("name", "疯狂Java讲义");
- return Action.SUCCESS;
- }
- // 使用注解来改变该成员变量序列化后的名字
- @JSON(name = "newName")
- public Map getMap() {
- return this.map;
- }
- // customName的setter和getter方法
- // field1的setter和getter方法
- // field2的setter和getter方法
- // field3的setter和getter方法
- }

打开后拉到最下面,进入JSON Result:

2564

被折叠的 条评论
为什么被折叠?



