从toString()/fromString()角度看JSON(1)

JSON是极常用的一种数据结构,跨语言,且可以与String非常友好地互相转化,尤其在客户端与服务器的http接口通讯中广泛使用。本文从JSON与String相互转化的角度介绍一下Android下JSON的源代码。源代码及测试手机环境为Android 6.0.1。

1.从一段测试代码开始

测试代码:

    private void testJson2() {
        try {

            JSONObject sonJsonObject = new JSONObject();
            sonJsonObject.put("sonkey1", "sonvalue1");
            sonJsonObject.put("sonkey2", "sonvalue2");

            JSONObject jsonObject1 = new JSONObject();
            JSONObject jsonObject2 = new JSONObject();

            jsonObject1.put("key1", "value1");
            jsonObject1.put("son", sonJsonObject);

            jsonObject2.put("key1", "value1");
            jsonObject2.put("son", sonJsonObject.toString());

            Log.i("TESTJSON", "json1 :\n" + jsonObject1.toString());
            Log.i("TESTJSON", "json2 :\n" + jsonObject2.toString());

            JSONObject dad1 = new JSONObject();
            dad1.put("he", jsonObject2);
            JSONObject dad2 = new JSONObject();
            dad2.put("he", jsonObject2.toString());

            Log.i("TESTJSON", "dad1 :\n" + dad1.toString());
            Log.i("TESTJSON", "dad2 :\n" + dad2.toString());

            JSONObject dad = new JSONObject();
            dad.put("he", dad2.toString());
            Log.i("TESTJSON", "dad :\n" + dad.toString());

        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

我们知道,JSON是一种递归的数据结构,可以通过put()方法将一个对象与KEY搭配放入JSON对象作为一个字段。与此对应,通过get()方法从JSON对象中得到该字段对应的值。put()/get()方法很友好,支持基本数据类型的自动装箱拆箱。
本文探讨JSON与String的关系,所以上述测试代码以JSONObject和String对象对比测试。
首先创建一个JSONObject:sonJsonObject,有两个String型的字段sonkey1、sonkey2。
创建两个JSONObject:jsonObject1、jsonObject2,将sonJsonObject分别以其自身、通过toString()转化得到的String对象放入son字段。
再创建两个上一级的JSONObject:dad1、dad2,将jsonObject2分别以其自身、通过toString()转化得到的String对象放入he字段。
最后创建一个最高一级JSONObject:dad,将dad2通过toString()转化得到的String对象放入he字段。
最后,将上述几个对象通过toString()转成String通过log打印出来。

ouput log:

09-07 17:39:22.294  6887  6887 I TESTJSON: json1 :
09-07 17:39:22.294  6887  6887 I TESTJSON: {"key1":"value1","son":{"sonkey1":"sonvalue1","sonkey2":"sonvalue2"}}
09-07 17:39:22.294  6887  6887 I TESTJSON: json2 :
09-07 17:39:22.294  6887  6887 I TESTJSON: {"key1":"value1","son":"{\"sonkey1\":\"sonvalue1\",\"sonkey2\":\"sonvalue2\"}"}
09-07 17:39:22.294  6887  6887 I TESTJSON: dad1 :
09-07 17:39:22.294  6887  6887 I TESTJSON: {"he":{"key1":"value1","son":"{\"sonkey1\":\"sonvalue1\",\"sonkey2\":\"sonvalue2\"}"}}
09-07 17:39:22.294  6887  6887 I TESTJSON: dad2 :
09-07 17:39:22.294  6887  6887 I TESTJSON: {"he":"{\"key1\":\"value1\",\"son\":\"{\\\"sonkey1\\\":\\\"sonvalue1\\\",\\\"sonkey2\\\":\\\"sonvalue2\\\"}\"}"}
09-07 17:39:22.294  6887  6887 I TESTJSON: dad :
09-07 17:39:22.294  6887  6887 I TESTJSON: {"he":"{\"he\":\"{\\\"key1\\\":\\\"value1\\\",\\\"son\\\":\\\"{\\\\\\\"sonkey1\\\\\\\":\\\\\\\"sonvalue1\\\\\\\",\\\\\\\"sonkey2\\\\\\\":\\\\\\\"sonvalue2\\\\\\\"}\\\"}\"}"}

从log上看到,通过JSONObject.toString()得到String放入的字段,会开始有转义字符出现,在层级最多的dad对象,转义符累积到7层。下面先从JSONObject.toString()的角度解释一下。

2.toString()

JSONObject.toString()源代码:


    /**
     * Encodes this object as a compact JSON string, such as:
     * <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
     */
    @Override public String toString() {
        try {
            JSONStringer stringer = new JSONStringer();
            writeTo(stringer);
            return stringer.toString();
        } catch (JSONException e) {
            return null;
        }
    }


    void writeTo(JSONStringer stringer) throws JSONException {
        stringer.object();
        for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
            stringer.key(entry.getKey()).value(entry.getValue());
        }
        stringer.endObject();
    }

使用了一个辅助类JSONStringer。开始和结束分别调用了object()/endObject(),中间是一个for循环。先看两头:

    /**
     * Begins encoding a new object. Each call to this method must be paired
     * with a call to {@link #endObject}.
     *
     * @return this stringer.
     */
    public JSONStringer object() throws JSONException {
        return open(Scope.EMPTY_OBJECT, "{");
    }

    /**
     * Ends encoding the current object.
     *
     * @return this stringer.
     */
    public JSONStringer endObject() throws JSONException {
        return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
    }

继续看open()/close():

    /**
     * Enters a new scope by appending any necessary whitespace and the given
     * bracket.
     */
    JSONStringer open(Scope empty, String openBracket) throws JSONException {
        if (stack.isEmpty() && out.length() > 0) {
            throw new JSONException("Nesting problem: multiple top-level roots");
        }
        beforeValue();
        stack.add(empty);
        out.append(openBracket);
        return this;
    }

    /**
     * Closes the current scope by appending any necessary whitespace and the
     * given bracket.
     */
    JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
        Scope context = peek();
        if (context != nonempty && context != empty) {
            throw new JSONException("Nesting problem");
        }

        stack.remove(stack.size() - 1);
        if (context == nonempty) {
            newline();
        }
        out.append(closeBracket);
        return this;
    }

    /**
     * Returns the value on the top of the stack.
     */
    private Scope peek() throws JSONException {
        if (stack.isEmpty()) {
            throw new JSONException("Nesting problem");
        }
        return stack.get(stack.size() - 1);
    }

出现两个缓存数据结构stack和out:

    /**
     * Unlike the original implementation, this stack isn't limited to 20
     * levels of nesting.
     */
    private final List<Scope> stack = new ArrayList<Scope>();

    /**
     * Lexical scoping elements within this stringer, necessary to insert the
     * appropriate separator characters (ie. commas and colons) and to detect
     * nesting errors.
     */
    enum Scope { ... }

stack是一个ArrayList,起到栈的作用,JSONObject是一个递归的数据结构,用栈来辅助解析。peek()即从栈顶取元素。

    /** The output data, containing at most one top-level array or object. */
    final StringBuilder out = new StringBuilder();

out是一个StringBuilder,显然是用来辅助拼接输出的String。
搞清楚了数据结构,看object()+open()的逻辑:
(1)向stack栈顶压入Scope.EMPTY_OBJECT
(2)向out添加 {
endObject()+close()的逻辑:
(1)取出栈顶元素
(2)如果(1)取到的元素是Scope.NONEMPTY_OBJECT,通过方法newline()向out增加一行新的内容
(3)向out添加 }
可以以一个没有任何字段内容的JSONObject的toString()想象一下这个过程。

接下来看复杂的,JSONObject.writeTo(JSONStringer)中间的for循环部分:

    void writeTo(JSONStringer stringer) throws JSONException {
        stringer.object();
        for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
            stringer.key(entry.getKey()).value(entry.getValue());
        }
        stringer.endObject();
    }

出现了两个重要的方法key()/value()。先看kay():

    /**
     * Encodes the key (property name) to this stringer.
     *
     * @param name the name of the forthcoming value. May not be null.
     * @return this stringer.
     */
    public JSONStringer key(String name) throws JSONException {
        if (name == null) {
            throw new JSONException("Names must be non-null");
        }
        beforeKey();
        string(name);
        return this;
    }

beforeKey()作用是校验stack栈顶值是否正确,添加逗号,增加新行,维护栈顶值状态机:

    /**
     * Inserts any necessary separators and whitespace before a name. Also
     * adjusts the stack to expect the key's value.
     */
    private void beforeKey() throws JSONException {
        Scope context = peek();
        if (context == Scope.NONEMPTY_OBJECT) { // first in object
            out.append(',');
        } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
            throw new JSONException("Nesting problem");
        }
        newline();
        replaceTop(Scope.DANGLING_KEY);
    }

string()稍后一起看。接下来看看value(Object):

    /**
     * Encodes {@code value}.
     *
     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
     *     Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
     *     or {@link Double#isInfinite() infinities}.
     * @return this stringer.
     */
    public JSONStringer value(Object value) throws JSONException {
        if (stack.isEmpty()) {
            throw new JSONException("Nesting problem");
        }

        if (value instanceof JSONArray) {
            ((JSONArray) value).writeTo(this);
            return this;

        } else if (value instanceof JSONObject) {
            ((JSONObject) value).writeTo(this);
            return this;
        }

        beforeValue();

        if (value == null
                || value instanceof Boolean
                || value == JSONObject.NULL) {
            out.append(value);

        } else if (value instanceof Number) {
            out.append(JSONObject.numberToString((Number) value));

        } else {
            string(value.toString());
        }

        return this;
    }

(1)这里看到了递归调用writeTo():如果value是JSONObject或者JSONArray,那么调用value.writeTo(this)。
(2)如果value是Boolean或者Number,那么直接(或者简单转化后)添加到out。
(3)其他情况的Object,和key的处理一样,调用string()处理。
(4)在(1)和(2)之间调用了beforeValue(),校验stack栈顶值是否正确,添加逗号及冒号,增加新行,维护栈顶值状态机:

    /**
     * Inserts any necessary separators and whitespace before a literal value,
     * inline array, or inline object. Also adjusts the stack to expect either a
     * closing bracket or another element.
     */
    private void beforeValue() throws JSONException {
        if (stack.isEmpty()) {
            return;
        }

        Scope context = peek();
        if (context == Scope.EMPTY_ARRAY) { // first in array
            replaceTop(Scope.NONEMPTY_ARRAY);
            newline();
        } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
            out.append(',');
            newline();
        } else if (context == Scope.DANGLING_KEY) { // value for key
            out.append(indent == null ? ":" : ": ");
            replaceTop(Scope.NONEMPTY_OBJECT);
        } else if (context != Scope.NULL) {
            throw new JSONException("Nesting problem");
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值