JSON解析类库之Gson(6) --- 从指定的流中读取Json,并反序列化为指定类的对象

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浏览器设置编码需要下载插件进行设置,具体方法自行百度):
 
下面我用是360极速浏览器(她是基于chrome的内核、同时集成了ie6--ie11的内核,是开发的好工具),我设置了浏览器编码为自动检测,她就会帮我自动设为UTF-8. 如图所示:
 
 
知道这个接口(http://wthrcdn.etouch.cn/weather_mini?city=广州),能返回JSON数据后,下面我就在程序中用http请求该接口,从而获取Json数据,并利用Gson类库对Json数据进行解析(反序列化)。
 
测试类:


当关于利用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()会自动connect
        httpConn.connect();
        // 获得响应状态码
        int resultCode = httpConn.getResponseCode();
        if (HttpURLConnection.HTTP_OK == resultCode) { // 200
            StringBuffer 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()会自动connect
        httpConn.connect();
        // 建立一个输出流,向建立的连接传入请求参数
        OutputStream os = httpConn.getOutputStream();
        os.write(param.getBytes());//传入参数到流中,输出流会写到连接中,参数会添加到http请求的正文里
        os.flush();
        os.close(); //当close之后不能再向连接中写入东西
        // 获得响应状态码
        int resultCode = httpConn.getResponseCode();
        if (HttpURLConnection.HTTP_OK == resultCode) { // 200
            StringBuffer sb = new StringBuffer();
            String readLine;
            InputStream in = httpConn.getInputStream(); // 其实这一步获得的流已经为空了
            GZIPInputStream gzin = new GZIPInputStream(in); // 运行到这一步报错EOFException
            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());
        }
        // 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.EOFException
    at 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,花了我很多时间~~~








评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值