`
bantouyan
  • 浏览: 147419 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

解析Json文本——如何将Json文本转化为Java对象

    博客分类:
  • Json
阅读更多

      Json是一种简单小巧的数据交换格式,在Web开发中获得了广泛应用。网络上有很多Json库,光用Java编写的就不下二十个之多。无论哪一个Json库都必须具有一个基本功能,就是把Json文本转换为用本语言表示的数据结构,本文就是介绍如何把Json文本一字符一字符的解析成Java对象。

      如果要问解析Json需要哪些基础知识的话,计算机科班出身的读者立马就能想到大学时学过的编译原理这门课程。解析Json就是需要利用编译原理的知识,不过Json非常简单,解析它不必使用所有的编译技术,只要了解词法分析就可以了。不了解词法分析也不要紧,Json非常简单,不用词法分析也能解析。本文根据bantouyan-json库中解析Json文本的方法编写,不需要词法分析的基础。

 

      在介绍怎样解析Json文本之前,我们先回顾一下Json的定义。如果要了解Json的详细定义可以查看RFC4627文档,或者www.json.org网站。本文只作简要的介绍。


Json对象


Json数组

 

Json Value的定义

 

Json String

 

Json Number

      上面的五幅图像分别定义了Json的对象、数组、Value、字符串与数字。Json文本由一个Json对象或Json数组构成,无论是Json对象或者Json数组,还是Json字符串或数字,都是一个Json Value。

      Json文本由构成Json的必要字符和额外的空白字符构成,解析它就要忽略额外的空白字符并将剩余部分转换为Java对象。本文按照bantouyan-json库提供的方法解析Json文本,介绍时做了一些适当的简化。

 

      bantouyan-json库的解析功能由内部类JsonTextParser提供,JsonTextParser类的定义如下:

class JsonTextParser
{
    private Reader reader;
    private int ch = -1;

    public JsonTextParser(String text)throws IOException
    {
        StringReader strReader = new StringReader(text); 
        this.reader = strReader;
        next();
    }

    private void next() throws IOException
    {
        ch = reader.read();
    }
}

JsonTextParser类的核心成员有reader和ch,reader存储要解析的字符串,ch表示当前被扫描的字符,方法next()每执行一次扫描一个字符,当ch的值是-1时表明字符串扫描结束。

      在详细介绍解析算法之前,先介绍JsonTextParser类的几个关键方法,

boolean isBlankCharacter(int ch),判断字符ch是不是空白字符;void parseTailBlank(int endChar) 与void parseTailBlank(int endChar1, int endChar2),扫描并Json文本,在遇到终止字符endChar之前如果已经扫描完Json文本则抛出异常,扫描到第一个终止字符时退出该方法。

 

      Json文本的解析从方法parse()开始(一个JsonTextParser对象方法parse只能被执行一次,这样设计也许并不合理,但它是内部类,不影响使用),其代码如下:

public Json parse() throws IOException, JsonException
{
    Json json = null;

    while(ch != -1)
    {
        if(ch == '{')
        {
            json = parseObject();
            parseTailBlank(-1);
        }
        else if(ch == '[')
        {
            json = parseArray();
            parseTailBlank(-1);
        }
        else if(! isBlankCharacter(ch))
        {
            // throw exception
        }
                
        next();
    }

    return json;
}

parseTailBlank(-1)表示扫描完整个字符串前不允许出现非空白字符。Json文本只允许表示一个对象或数组,所以在字符“[”、“{”之前不允许出现非空白字符,扫描完第一个对象或数组(正常情况下只有一个)后也不允许再出现非空白字符。

 

      方法parseJsonObject负责扫描并解析一个Json对象,调用该方法时正好扫描到Json对象的开始字符'{',得到Json对象后,扫描到对象的结束字符'}'的下一个字符时退出,parseJsonObject的代码如下:

private JsonObject parseObject() throws IOException, JsonException
{
    JsonObject json = new JsonObject();
    boolean needNextElement = false;
        
    next(); //skip character '{'
        
    while(ch != -1)
    {
        //还没有遇到JsonObject的子元素,扫描到'}'可直接结束
     if(needNextElement == false && ch == '}') break;
    
        if(isBlankCharacter(ch))
        {
            next(); //skip blank character
        }
        else
        {
            String name = parseName();
            Json value = parseValue('}');
            parseTailBlank(',', '}');
            json.set(name, value);
        
            if (ch == '}') //子元素后是'}',JsonObject结束
            {
                break;
            } 
            else //子元素后是',',需解析下一个子元素(Name Value对)
            {
                next(); // skip character ','
                needNextElement = true;
            }
        }
    }

    if(ch == '}')
    {
        next(); // skip character '}'
    }
    else
    {
        // throw exception
    }

    return json;
}

方法parseName 负责解析JsonObject子元素的Name部分(包括分界符':'),parseValue负责解析JsonObject子元素的Value部分。

 

      方法parseJsonArray负责扫描并解析一个Json数组,调用该方法时正好扫描到Json数组的开始字符'[',得到Json数组后,扫描到数组的结束字符']'的下一个字符时退出,parseJsonArray的代码如下:

private JsonArray parseArray() throws IOException, JsonException
{
    JsonArray json = new JsonArray();  
    boolean needNextElement = false;  

    next(); // skip character '['
        
    while(ch != -1)
    {
        //还没有遇到JsonArray的子元素,扫描到']'可直接结束
        if(needNextElement == false && ch == ']') break;
            
        if (isBlankCharacter(ch))
        {
            next(); //skip blank character
        }
        else
        {
            Json value = parseValue(']');
            json.append(value);
            parseTailBlank(',', ']');
            if (ch == ']') //子元素后是']',数组结束
            {
                break;
            } 
            else //子元素后是',',需解析下一个子元素
            {
                next(); // skip character ','
                needNextElement = true;
            }
        }
    }
        
    if(ch == ']')
    {
            next(); // skip character ']'
    }
    else
    {
        // throw exception
    }
        
    return json;
}

方法parseValue负责解析Json数组的子元素。

 

     方法parseName负责扫描并解析Json对象子元素的Name部分,调用该方法时正好扫描到Name部分的第一个字符(非空白字符),得到Name后,扫描到Name部分的结束字符':'的下一个字符时退出,parseName的代码如下:

private String parseName() throws IOException, JsonException
{
    String name = null;
    name = parseString(ch);
    parseTailBlank(':');
    
    if(ch == ':') //Name部分正常结束
    {
        next(); //skip character ':'
    }
    else
    {
        // throw exception
    }
    
    return name;
}

JsonObject子元素的Name部分就是一个字符串,方法parseName扫描这个字符串和随后的结束符':' 。

 

      parseJsonObject与parseJsonArray都调用到方法parseValue, 方法parseValue负责解析一个Json Value。在Json中,Value可以是Json对象、Json数组、Json字符串、Json数字、true、false或null,它出现在Json对象子元素的Value部分和Json数组的子元素处,把这两处用到的代码归纳在一起,就是方法parseValue。调用parseValue时正好扫描到Value部分的第一个字符(可以是Value部分的第一个前导空白字符),扫描完Value部分(不包括后缀空白字符)后遇到下一个字符时退出,parseValue的代码如下:

private Json parseValue(int endChar) throws IOException, JsonException
{
    Json json = null;

    while(ch != -1)
    {
        if(isBlankCharacter(ch))
        {
            next(); // skip blank character
        }
        else if(ch == '{')
        {
            json = parseObject();
            break;
        }
        else if(ch == '[')
        {
            json = parseArray();
            break;
        }
        else if(ch == 't') // parse true
        {
            json = parseJsonConstant("true", trueAry, endChar);
            //trueAry是一个私有静态数组,内容是{'t', 'r', 'u', 'e'}
            break;
        }
        else if(ch == 'f') //parse false
        {
            json = parseJsonConstant("false", falseAry, endChar);
            //falseAry是一个私有静态数组,内容是{'f', 'a', 'l', 's', 'e'}
            break;
        }
        else if(ch == 'n') //parse null
        {
            json = parseJsonConstant("null", nullAry, endChar);
            //nullAry是一个私有静态数组,内容是{'n', 'u', 'l', 'l'}
            break;
        }
        else if(ch == '\"')
        {
            String str = parseString();
            json = new JsonPrimitive(str);
            break;
        }
        else if(ch == '-' || (ch >= '0' && ch<= '9'))
        {
            Number num = parseNumber(endChar);
            json = new JsonPrimitive(num);
            break;
        }
        else 
        {
            // throw exception
        }
    }
        
    return json;
}

Value部分第一个非空白字符如果是'{'则Value是一个Json对象,如果是字符'[' 则Value是一个Json数组,如果是字符'"'则肯定是Json字符串,如果是字符'-'或字符'0'到'9'则肯定是Json数字,如果是字符't'则肯定是Json常量true,如果是字符'f'则肯定是常量false,如果是字符'n'则肯定是常量null,否则肯定Json文本格式有误,因而无法解析。

 

      parseJsonObject与parseJsonArray前面都已经给予了介绍,下面介绍剩余的方法。

 

      方法parseString负责扫描并解析一个Json字符串,调用该方法时正好扫描到Json字符串的开始字符'"',扫描完Json字符串后,扫描到字符串的结束字符'"'的下一个字符时退出,parseString的代码如下:

private String parseString() throws IOException, JsonException
{
    StringBuilder build = new StringBuilder();
    next(); // skip quatorChar "
        
    while(ch != -1)
    {
        if(ch == '"') break;

        if(ch < 0x0020)
        {
            // throw exception
        }
        if(ch == '\\')
        {
            next();
            switch(ch)
            {
                case '\"':
                    ch = '\"';
                    break;
                case '\'':
                    ch = '\'';
                    break;
                case '\\':
                    ch = '\\';
                    break;
                case '/':
                    ch = '/';
                    break;
                case 'b':
                    ch = '\b';
                    break;
                case 'f':
                    ch = '\f';
                    break;
                case 'n':
                    ch = '\n';
                    break;
                case 'r':
                    ch = '\r';
                    break;
                case 't':
                    ch = '\t';
                    break;
                case 'u':
                    for(int i=0; i<4; i++)
                    {
                        next();
                        if((ch >='0' && ch <='9') || (ch >= 'a' && ch <='f')
                                || (ch>= 'A' && ch <= 'F'))
                        {
                            unicodeChar[i] = (char)ch;
                            // unicodeChar是一个私有字符数组,长度为4
                        }
                        else
                        {
                            // throw exception
                        }
                                
                    }
                    ch = Integer.parseInt(new String(unicodeChar), 16);
                    break;
                default:
                    // throw exception
            }
        }
        build.append((char)ch);

        next();
    }

    if(ch == '"')
    {
        next(); // skip quator char
    }
    else
    {
        // throw exception
    }

    return build.toString();
}

解析Json字符串的关键在于处理转义字符序列,要能够恰当的将转义字符序列转换为对应的字符,遇到非法转义字符时要报告错误。

 

      Json中的数字与通常意义上的数字字面量有点不同,它不允许有不必要的前导零,也不允许有前导符号'+'。方法parseNumber负责扫描并解析一个Json数子,调用该方法时正好扫描到Json数字的开始字符,扫描完Json数字后,扫描到数字的下一个字符时退出,parseString的代码如下:

private Number parseNumber(int endChar) throws IOException, JsonException
{
    StringBuilder build = new StringBuilder();
    boolean isInt = true;
        
    // parse minus sign
    if(ch == '-')
    {
        build.append((char)ch);
        next();
    }
        
    //parse integer part
    if(ch == '0') //begin with 0
    {
        build.append((char)ch);
        next();
        if(ch >= '0' && ch <= '9')
        {
            // throw exception
        }
    }
    else if(ch > '0' && ch <= '9') //begin with 1..9
    {
        build.append((char)ch);
        next();

        while(ch != -1)
        {
            if(ch >= '0' && ch <= '9')
            {
                build.append((char)ch);
            }
            else
            {
                break;
            }
            next();
        }
    }
    else
    {
        // throw exception
    }
        
    //parse fraction
    if(ch == '.')
    {
        build.append((char)ch);
        isInt = false;            
        next(); //skip character '.'
        
        if(ch>='0' && ch<='9')
        {
            while(ch != -1)
            {
                if(ch>='0' && ch<='9')
                {
                    build.append((char)ch);
                }
                else
                {
                    break;
                }
                next();
            }
        }
        else
        {
            // throw exception
        }  
    }
        
    // parse exponent
    if(ch == 'e' || ch == 'E')
    {
        build.append((char)ch);
        isInt = false;
        next(); //skip character e
    
        //parse plus or minus sign
        if(ch == '+' || ch == '-')
        {
            build.append((char)ch);
            next();
        }
    
        if(ch>='0' && ch<='9')
        {
            while(ch != -1)
            {
                if(ch>='0' && ch<='9')
                {
                    build.append((char)ch);
                }
                else
                {
                    break;
                }
                next();
            }
        }
        else
        {
            // throw exception
        }
    }
    if(ch != ',' && ch != endChar && !isBlankCharacter(ch))
    {
        // throw exception
    }

    String numStr = build.toString();
    try
    {
        if(isInt)
        {
            return Long.parseLong(numStr);
        }
        else
        {
            return Double.parseDouble(numStr);
        }
    } 
    catch (NumberFormatException e)
    {
        // throw exception
    }
}

解析Json数字时使用一个StringBuilder存储表示数字的字符串,一边扫描一遍检查数字格式,最后根据扫描到的字符串能否转换为整数决定是用Long.parseLong(String)解析还是用Double.parseDouble(String)解析。

 

      方法parseJsonConstant()负责解析Json常量true、false和null,调用时正好扫描到常量的第一个字符,退出时扫描到常量的下一个字符时退出,如果常量的下一个字符既不是分界符也不是是空白字符,那么认为所扫描到的不是一个Json常量处理。parseJsonConstant()的代码如下:

private JsonPrimitive parseJsonConstant(String constName, int[] constAry, int endChar)
throws IOException, JsonException
{
    JsonPrimitive json = null;

    for(int i=0; i<constAry.length; i++)
    {
        if(ch != constAry[i])
        {
            // throw exception
        }
            
        next();
    }
    if(ch != ',' && ch != endChar && !isBlankCharacter(ch))
    {
        // throw exception
    }
        
    json = (constAry == trueAry)? Json.trueJson: 
                    (constAry == falseAry)? Json.falseJson: Json.nullJson;
        
    return json;
}

参数endChar表示Json常量后除','外允许跟的另外一个分界符,参数constAry用于比较所扫描的字符,参数constName用来报告更直观的异常。

 

      本文介绍的算法对字符串只进行一次扫描,不需要回溯,因此时间复杂度为O(n)。一遍扫描的好处是不用回溯,算法简单,坏处是如果被解析的字符串格式不正确,那么只能报告所发现的第一个错误。好在大多数情况下本解析的Json文本都是正确的,而且代码一般也不负责修复Json文本的错误,所以采用一次扫描的策略是可行的。

 

 

 

相关阅读:

解析Json——bantouyan-json库概述

解析Json——Json类的静态方法

解析Json——Json类的实例方法

解析Json——操纵JsonObject

解析Json——操纵JsonArray

4
7
分享到:
评论
1 楼 二飞在这里 2015-05-20  
你好,这个代码里有很多方法都没有写呢,比如parseTailBlank(),isBlankCharacter()等,所以程序里有很多错误。请问,这可如何是好。

相关推荐

    json系列文章——json的使用

    3. JSON的编码与解码:在其他语言中,如Python有json库,Java有org.json库,用于将JSON字符串转化为语言中的数据结构,或者将语言中的数据结构转化为JSON字符串。这些库提供了编码(encode)和解码(decode)的方法...

    JSON ——数据库结果集转换

    标题和描述均提到了“JSON —— 数据库结果集转换”,这主要涉及到将数据库查询结果转化为JSON格式的数据,以便于在网络传输中使用。在现代Web开发中,JSON(JavaScript Object Notation)是一种轻量级的数据交换...

    一个Java序列化反序列化库,用于将Java对象转换为JSON和返回JSON.zip

    Gson是Google提供的一个开源库,它能够将Java对象转换为JSON字符串,同时也能将JSON文本解析回等效的Java对象。Gson库的强大之处在于其灵活性和易用性,开发者无需编写大量的适配代码,就能实现Java对象和JSON之间的...

    json lib jdk

    1. **解析JSON**:通过`JsonParser`类,可以将一个JSON格式的字符串解析成一个`JsonNode`对象,进一步转化为Java对象,如Map、List或自定义类实例。 2. **生成JSON**:利用`JsonGenerator`类,可以将Java对象转换成...

    一个java处理JSON格式数据的通用类.pdf

    本文档将详细介绍一个用于Java中处理JSON数据的通用类——`JsonUtil`,该类封装了一系列方法用于实现JSON与Java对象之间的转换。 #### 类结构及包引入 `JsonUtil`位于`com.linghui.common.util`包下。为了实现JSON...

    生成JSON树型表结构

    总的来说,生成JSON树型表结构是将层级数据转换为易于EXT树组件解析的格式,通过合理的数据库查询和后端处理,结合前端EXT框架,可以实现高效且美观的树形数据展示。这个过程涉及到数据结构、JSON序列化、前端UI组件...

    gson-2.3.1.zip

    2. `JsonParser`:用于解析JSON文本,将其转换为`JsonElement`,这可以进一步解析为`JsonObject`、`JsonArray`等。 3. `JsonElement`:表示JSON结构的基本元素,包括`JsonObject`(键值对)、`JsonArray`(数组)、...

    android使用JSON进行网络数据交换(服务端、客户端)的实现.zip源码资源下载

    6. 将响应内容转化为JSON字符串,使用JSONObject或JSONArray解析。 五、服务端处理 1. 服务端通常使用Java的JSON库如Jackson、Gson或org.json来生成JSON响应。 2. 处理客户端请求,根据业务逻辑构建JSON对象或数组...

    google-gson-2.2.4.zip

    通过`Gson().fromJson()`方法,你可以将JSON文本转化为对应的Java类型,如类、数组或集合。 3. **类型适配器**:Gson允许用户自定义类型适配器,以便处理特定类型的序列化和反序列化。这对于处理自定义日期格式、...

    gson-2.2.jar.zip

    例如,一个Java类的实例可以通过这种方式转化为JSON文本,便于在网络上传输或存储。 2. **JSON到对象**:Gson也支持将JSON字符串反序列化为Java对象。使用`fromJson()`方法,可以将JSON字符串转换为指定类型的Java...

    aip-java-sdk-4.1.0.zip )

    《基于OCR技术的图片文字识别——深度解析百度aip-java-sdk-4.1.0》 在当今数字化时代,图像中的文字识别(OCR,Optical Character Recognition)技术扮演着至关重要的角色,它使得计算机能够自动识别并转换图像中...

    阿里fastjson.jar

    Fastjson的命名来源于"Fast Java Object to JSON",即快速地将Java对象转换为JSON字符串,同时也支持从JSON文本反序列化到Java对象。 标题中的"阿里fastjson.jar"指的是Fastjson的核心库文件,这是一个可执行的JAR...

    IloveMarshmallows:Android项目-JSON解析器-Zappos

    最后,使用Gson的fromJson方法将接收到的JSON字符串转化为Java对象,便于进一步处理和展示。 5. JSON解析实例: 假设我们从Zappos API获取的商品数据如下: ```json { "products": [ { "name": "Nike Running...

    UserRegistration

    这里的`@RequestBody`会将接收到的JSON数据自动转化为`User`对象,`User`类需要与JSON结构匹配,属性对应JSON键。完成后,通过`userService.register(user)`调用服务层进行实际的注册操作。 数据库操作通常使用ORM...

    tableConverter-emstudio-freeems:从EMStudio的json文件转换为FreeEMS固件的.h文件样式

    这些库能帮助我们快速、准确地将JSON文本转化为Java对象模型。 2. **数据结构映射**:设计Java类来表示JSON文件中的各个参数,如`SensorConfig`和`ActuatorConfig`。这些类将持有JSON文件中的键值对,并提供方便的...

    java课程设计.rar

    可以使用Java的I/O流进行文件读写,如序列化技术将对象转化为二进制流保存到文件,或者直接以文本格式(如JSON、XML)存储数据。 4. **异常处理** - 为了保证程序的健壮性,需要对可能出现的异常进行处理。例如,...

    Android源码——笑话故事android应用源码.zip

    这有助于理解如何将接收到的网络数据转化为Java对象。 6. **数据存储**:如果应用有离线缓存功能,可能会用到SQLite数据库或`SharedPreferences`。这部分源码展示了如何在Android中持久化数据。 7. **事件监听**:...

    xml实用技术教程—— 顾兵

    1. DOM(Document Object Model):DOM将XML文档解析为一棵树形结构,通过编程语言访问和操作节点,实现数据的读取和修改。 2. SAX(Simple API for XML):SAX是一种基于事件驱动的解析方式,对XML文档进行流式...

    安卓开发-【手机安全卫士02】连接服务器获取更新信息.zip

    使用Retrofit,我们可以将网络请求接口定义为Java方法,通过注解来指定HTTP方法(GET、POST等)和URL,然后通过Gson或其它转换库将服务器返回的JSON数据转化为Java对象。 接下来,我们需要设计服务器端的接口,用于...

    安卓Android源码——微博客户端源代码.zip

    5. **JSON解析**:使用Gson或Jackson库解析服务器返回的JSON数据,将其转化为Java对象。 6. **数据存储**:SQLite数据库用于存储用户数据,如关注列表、好友列表、微博内容等。 7. **异步处理**:使用AsyncTask或...

Global site tag (gtag.js) - Google Analytics