JSON解析类库之Gson(6) --- 从指定的字符流(Reader)中读取Json,并反序列化为指定类的对象
---Gson类库学习, 生成与解析json数据,json字符串与Java对象互转
一、前言
之前的JSON字符串我们都是通过本地的Java对象序列化后得到的,或者直接定义了一个String类型的Json字符串。实际开发中,我们都是通过调用其他程序模块的接口而获得需要的Json数据,从而解析。或者读取一个本地的Json文件进行解析。
二、从指定的字符流(Reader)中读取Json,并反序列化为指定类的对象
◆
◆
◆
获取Json字符串的流,并进行反序列化
----------------------------------------------------------------------------------------------------
使用API如下:
public <T> T fromJson(Reader json, Class<T> classOfT)
public <T> T fromJson(Reader json, Type typeOfT)
之前我们介绍的都是先Java对象序列化,获得序列化后的Json字符串,然后利用Json字符串进行反序列化。
实际开发中,序列化多半是本地生成Java对象,然后进行序列化。而反序列化
通常有两种情况:
一种是:
通常在向外提供接口时使用序列化,将我们处理后封装好的数据序列化成Json字符串,提一个接口,供其他程序调用。其他程序通过http调用接口获得Json数据,然后解析Json字符串获得我们想要的数据。
比如,我们可以调用天气预报网站提供的接口,获得他们返回的Json格式的天气数据,然后我们解析Json数据为我们的系统所用。而http接口调用,我们都是从连接中获取Json字符串的流,从流中解析Json。
第二种:
我们从一个Json字符串文件流解析Json数据,有可能是本地的一个文件,比如,d:\user.json、或者d:\user.txt,只要文件里的数据是Json格式数据就行。
两种类型的处理的一样,只是待处理Json的JSON流的获取方式不一样而已,一个是http远程调用获得Json流,一个是本地文件中获得Json流。
◆
◆
<1>
通过读取本地文件获得Json的流,并反序列化为指定类型的Java对象
-----------------------------------------------------------------------
说明:
下面进行讲解。
现在有两个文件(里面内容相同)
srcDeptJsonString.txt,srcDeptJsonString.json,里面的内容为:
{"id": "A001", "deptName": "研发部", "users": [ { "id": "1", "name": "王重阳", "birthday": "2017-05-03 14:52:11" }, { "id": "2", "name": "郭靖", "birthday": "2017-05-03 14:52:11" }, { "id": "3", "name": "黄蓉", "birthday": "2017-05-03 14:52:11" } ] }
创建一个文本文件:
另存为:
注意文件另存为UTF-8编码格式的,不然中文解析时会出现乱码。
使用API如下:
public <T> T fromJson(Reader json, Class<T> classOfT)
实体类1:
public class Dept {private String id;private String deptName;private List<User> users;//为了代码简洁,这里移除了getter和setter方法等}
实体类2:
public class User {private String id;private String name;private Date birthday;//为了代码简洁,这里移除了getter和setter方法等}
测试类:
package com.chunlynn.gson;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.Reader;import com.google.gson.Gson;import com.google.gson.GsonBuilder;public class GsonTest25 {public static void main(String[] args) throws IOException {FileInputStream in = new FileInputStream("d:\\srcDeptJsonString.txt");Reader reader = new InputStreamReader(in, "UTF-8");Gson gson = new GsonBuilder() .create();// 反序列化,从指定的流中将Json反序列化为指定java对象Dept user = gson.fromJson(reader, Dept.class);System.out.println("反序列化结果:\n" + user);/*** 反序列化结果:Dept [id=A001, deptName=研发部,users=[User [id=1, name=王重阳, birthday=Wed May 03 14:52:11 CST 2017],User [id=2, name=郭靖, birthday=Wed May 03 14:52:11 CST 2017],User [id=3, name=黄蓉, birthday=Wed May 03 14:52:11 CST 2017]]]*/}}
可以看到,我们通过读取本地磁盘上的文件,成功得解析了文件里面的Json,并反序列化为指定的对象。泛型处理类似,指定Type,并调用
public
<T> T fromJson(Reader json, Type typeOfT)
方法反序列化。
◆
◆
<2>
通过Http请求获得Json的流,并反序列化为指定类型的Java对象
-----------------------------------------------------------------------
使用的方法:
public
<T> T fromJson(Reader json, Class<T> classOfT)
public
<T> T fromJson(Reader json, Type typeOfT)
先来看看一个提供天气预报数据的一个接口:
http://wthrcdn.etouch.cn/weather_mini?city=广州
请求这个接口,接口会给你返回天气预报的详细信息,而且是JSON格式的数据(这正是我们想要的数据)。
注意浏览器编码要设置为UTF-8,不然会乱码,如下(chrome浏览器乱码,最新版的chrome浏览器设置编码需要下载插件进行设置,具体方法自行百度):
测试类:
当关于利用HttpURLConnection进行Http请求,请参考我的另一篇博文《Http请求远程接口获得Json数据 --- 利用HttpURLConnection发送post/get请求》。
当然http请求远程接口获得响应的数据流,还可以利用HttpClient类来处理,参考http系列第二篇。
package com.chunlynn.gson;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Reader;import java.net.HttpURLConnection;import java.net.URL;import java.net.URLEncoder;import java.util.zip.GZIPInputStream;import com.google.gson.Gson;import com.google.gson.GsonBuilder;public class GsonTest27 {public static void main(String[] args) throws IOException {/*** 通过Http获取天气信息*/String urlPath = new String("http://wthrcdn.etouch.cn/weather_mini?city=" + URLEncoder.encode("广州", "UTF-8"));URL url = new URL(urlPath);/*** 返回URLConnection实例,表示与URL引用的远程对象的连接。* 应该注意的是,URLConnection实例在创建时不会建立实际的网络连接。* 实际的网络连接只会在调用URLConnection.connect()时发生。*/HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();//设置参数httpConn.setRequestMethod("GET");// 设置GET方式连接httpConn.setDoOutput(true); // 需要输出httpConn.setDoInput(true); // 需要输入httpConn.setUseCaches(false); // 不允许缓存httpConn.setConnectTimeout(500000);httpConn.setReadTimeout(500000);// 设置请求属性httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");httpConn.setRequestProperty("Charset", "UTF-8");//必须设置httpConn.setInstanceFollowRedirects(true);// 实际连接,也可以不用显示connect,使用下面的httpConn.getInputStream()会自动connecthttpConn.connect();// 获得响应状态码int resultCode = httpConn.getResponseCode();if (HttpURLConnection.HTTP_OK == resultCode) { // 200StringBuffer sb = new StringBuffer();String readLine;//获得请求连接中,远程服务器返回的流InputStream in = httpConn.getInputStream();//由于远程返回的流是经过压缩编码的,所以要用GZIPInputStream类进行解压解码GZIPInputStream gzin = new GZIPInputStream(in);//[1]方式一(此方式仅供测试用):从流中解析JSON数据,读到StringBuffer的变量中转成String输出BufferedReader responseReader = new BufferedReader(new InputStreamReader(gzin, "UTF-8"));while ((readLine = responseReader.readLine()) != null) {sb.append(readLine);}responseReader.close();gzin.close();System.out.println(sb.toString());/*{"desc":"OK","status":1000, "data":{"wendu":"28","ganmao":"天气转凉,空气湿度较大,较易发生感冒,体质较弱的朋友请注意适当防护。","forecast":[{"fengxiang":"无持续风向","fengli":"微风级","high":"高温 29℃","type":"阴","low":"低温 23℃","date":"9日星期二"},{"fengxiang":"无持续风向","fengli":"微风级","high":"高温 31℃","type":"多云","low":"低温 24℃","date":"10日星期三"},{"fengxiang":"无持续风向","fengli":"微风级","high":"高温 31℃","type":"多云","low":"低温 24℃","date":"11日星期四"},{"fengxiang":"无持续风向","fengli":"微风级","high":"高温 30℃","type":"中雨","low":"低温 24℃","date":"12日星期五"},{"fengxiang":"无持续风向","fengli":"微风级","high":"高温 28℃","type":"中到大雨","low":"低温 24℃","date":"13日星期六"}],"yesterday":{"fl":"3-4级","fx":"南风","high":"高温 29℃","type":"大雨","low":"低温 22℃","date":"8日星期一"},"aqi":"45","city":"广州"}}*///[2]方式二(实际使用):将获取到的JSON的流,传入Gson中解析为Java对象Reader reader = new InputStreamReader(gzin, "UTF-8");Gson gson = new GsonBuilder().setPrettyPrinting().create();/*** 反序列化,这里要创建Java对象Weather,里面还有泛型字段成员。* 原理就是这样,请求远程接口获得JSON的流,然后用Gson反序列化解析,* 如果远程服务器接口返回的Json流 是经过gzip压缩编码的,则要用GZIPInputStream进行包装解压(解码)。* 其他压缩格式类似,都有对应的类。*/Weather Weather = gson.fromJson(reader, Weather.class);}}}
关于用来反序列化为指定的Weather对象,后续有时间再详细完成,就是一个复杂的JavaBean对象。我们通过http请求,获得一个包含天气信息的Json流,由于提供天气信息的服务器对向外响应的流是进行了压缩编码的(这样是为了节省网络传输资源,便于快速传输),所以我们得到响应流后也要进行解压(解码),否则数据是乱码的,无法解析。
HTTP压缩,在HTTP协议中,其实是内容编码的一种。
在HTTP协议中,可以对内容(也就是body部分)进行编码, 可以采用gzip这样的编码,从而达到压缩的目的。 也可以使用其他的编码把内容搅乱或加密,以此来防止未授权的第三方看到文档的内容。
所以我们说HTTP压缩,其实就是HTTP内容编码的一种。 所以大家不要把HTTP压缩和HTTP内容编码两个概念混淆了。
我们通过Fiddler工具进行模拟请求,来分析一下天气预报Json数据提供方(接口),返回给我们的数据到底是怎样的:
当关于利关于Fiddler工具是什么,以及使用方法,请参考我的另一篇博文《Fiddler模拟post和get请求,分析请求的参数,请求的数据》
Fiddler工具是JavaEE开发经常用到一个利器,可以用来模拟post/get请求,特别是post请求。可以分享请求响应的结果,响应头、响应内容、编码情况等等。非常好用,建议大家掌握。
分析头文件:
点击
Raw,查看原始数据,可以看到数据是乱码的,同时响应体中也标明了Content-Encoding:gzip,
如图中所示。
点击上面的解压(Response body is encoded.Click to decode) 后:
只要我们获得了JSON的流,就可以对他进行解析。利用这个Gson的这两个方法:
public
<T> T fromJson(Reader json, Class<T> classOfT)
public
<T> T fromJson(Reader json, Type typeOfT)
进行反序列化。
注意:这里我用的是GET请求,可以正确解压接口返回的Response流。但是当我用POST请求时,运行到
GZIPInputStream gzin = new GZIPInputStream(in);
这一步报错EOFException错误。具体原因百度了很多,发现很多网友也遇到了类似的问题。具体也没发现一个好的解决方法。下面附上POST请求天气预报接口,返回Json流的方法:
package com.chunlynn.gson;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.net.URLEncoder;import java.util.zip.GZIPInputStream;import com.google.gson.Gson;import com.google.gson.GsonBuilder;public class GsonTest26 {public static void main(String[] args) throws IOException {/*** 通过Http获取天气信息*/String urlPath = new String("http://wthrcdn.etouch.cn/weather_mini");String param = "city=" + URLEncoder.encode("广州", "UTF-8");URL url = new URL(urlPath);/*** 返回URLConnection实例,表示与URL引用的远程对象的连接。* 应该注意的是,URLConnection实例在创建时不会建立实际的网络连接。* 实际的网络连接只会在调用URLConnection.connect()时发生。*/HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();//设置参数httpConn.setRequestMethod("POST");// 设置POST方式连接httpConn.setDoOutput(true); // 需要输出httpConn.setDoInput(true); // 需要输入httpConn.setUseCaches(false); // 不允许缓存httpConn.setConnectTimeout(500000); //设置连接超时(可省略)httpConn.setReadTimeout(500000); //设置读取超时(可省略)// 设置请求属性httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");httpConn.setRequestProperty("Connection", "Keep-Alive");// 维持长连接(可不设置)httpConn.setRequestProperty("Charset", "UTF-8");//必须设置httpConn.setInstanceFollowRedirects(true);// 连接,也可以不用明文connect,使用下面的httpConn.getInputStream()会自动connecthttpConn.connect();// 建立一个输出流,向建立的连接传入请求参数OutputStream os = httpConn.getOutputStream();os.write(param.getBytes());//传入参数到流中,输出流会写到连接中,参数会添加到http请求的正文里os.flush();os.close(); //当close之后不能再向连接中写入东西// 获得响应状态码int resultCode = httpConn.getResponseCode();if (HttpURLConnection.HTTP_OK == resultCode) { // 200StringBuffer sb = new StringBuffer();String readLine;InputStream in = httpConn.getInputStream(); // 其实这一步获得的流已经为空了GZIPInputStream gzin = new GZIPInputStream(in); // 运行到这一步报错EOFExceptionBufferedReader responseReader = new BufferedReader(new InputStreamReader(gzin, "UTF-8"));while ((readLine = responseReader.readLine()) != null) {sb.append(readLine);}responseReader.close();gzin.close();System.out.println(sb.toString());}// Reader reader = new InputStreamReader(gzin, "UTF-8");//Gson gson = new GsonBuilder().create();// Weather Weather = gson.fromJson(reader, Weather.class);}}
遇到的异常如下【65行指的就是GZIPInputStream gzin = new GZIPInputStream(in)
这
一行】
Exception in thread "main" java.io.EOFExceptionat java.util.zip.GZIPInputStream.readUByte(GZIPInputStream.java:268)at java.util.zip.GZIPInputStream.readUShort(GZIPInputStream.java:258)at java.util.zip.GZIPInputStream.readHeader(GZIPInputStream.java:164)at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:79)at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:91)at com.chunlynn.gson.GsonTest26.main(GsonTest26.java:65)
如有朋友解决了POST请求上述天气预报这个接口,而获得gzip编码解压后的流的,请知会我,谢谢!
-----------------------------------------------------------
后来我用Fiddler进行POST模拟请求,发现服务器返回的数据为空,没有Response body返回。
也就说
这个天气预报接口(http://wthrcdn.etouch.cn/weather_mini?city=广州)只支持GET方式的请求,而不支持POST请求。完美解决这个bug,花了我很多时间~~~