Offer to come, dig friends take it! I am participating in the 2022 Spring Recruit Punch card activity. Click here for details.

preface

I recently encountered a problem with a JSON statement that contained a “/”, but toString was found to be different from the original string! Here is a record of the whole process of my research.

The phenomenon of

The code is as follows:

String str1 = "{\"id\":\"1/2\"}";
Log.e("ssss", str1.hashCode() + ":" + str1);
try {
    JSONObject obj = new JSONObject(str1);
    String str2 = obj.toString();
    Log.e("ssss", str2.hashCode() + ":" + str2);
} catch (JSONException e) {
    e.printStackTrace();
}
Copy the code

Print result:

E/ssss: -1703691961:{“id”:”1/2″}

E/ssss: -1233361487:{“id”:”1/2″}

You can see that the two strings are different. After JSON, the “/” is an extra “”, so the hashcode is the same. In some cases, this can cause some confusion, such as encryption, transmission and decryption.

explore

So why is this happening? There must be some processing in the toString method of JSONObject. Let’s look at it:

@Override @NonNull public String toString(a) {
    try {
        JSONStringer stringer = new JSONStringer();
        writeTo(stringer);
        return stringer.toString();
    } catch (JSONException e) {
        return null; }}Copy the code

As you can see, create a JSONStringer and call the writeTo function:

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();
}
Copy the code

Here we put JSON key-value pairs into JSONStringer objects, and then we look at the value function:

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;
}
Copy the code

As you can see, if value is JSONObject or JSONArray, writeTo parsing is performed layer by layer. Until value is not JSON, in which case it is not null, Boolean, or number, the string function is called to handle value.tostring (). So you can see that it’s not just toString, so what does string do?

private void string(String value) {
    out.append("\" ");
    for (int i = 0, length = value.length(); i < length; i++) {
        char c = value.charAt(i);

        /* * From RFC 4627, "All Unicode characters may be placed within the * quotation marks except for the characters that must be escaped: * quotation mark, reverse solidus, and the control characters * (U+0000 through U+001F)." */
        switch (c) {
            case '"':
            case '\ \':
            case '/':
                out.append('\ \').append(c);
                break;

            case '\t':
                out.append("\\t");
                break;

            case '\b':
                out.append("\\b");
                break;

            case '\n':
                out.append("\\n");
                break;

            case '\r':
                out.append("\\r");
                break;

            case '\f':
                out.append("\\f");
                break;

            default:
                if (c <= 0x1F) {
                    out.append(String.format("\\u%04x", (int) c));
                } else {
                    out.append(c);
                }
                break;
        }

    }
    out.append("\" ");
}
Copy the code

It can be seen that the processing is basically special symbols, such as \t, \n and so on, which also handle the “/”, with the “\” escape character in front of it, which actually changes the “/” to “/”.

Why is that?

Here we get to the root cause, but it’s still a bit of a temptation. Why is JSON doing this? There is no problem with a slash in a string, so why convert it?

As can be seen from “Escape characters” in Baidu Encyclopedia, there is no international regulation that “/” needs to be escaped. On the json website (www.json.org/), you can see this entry:

escape
    '"'
    '\' '/ ''b'
    'f'
    'n'
    'r'
    't'
    'u' hex hex hex hex
Copy the code

JSON specifies this item. Why does JSON specify this item?

<>

Let’s do a couple of cases

  • 1. If there are multiple slashes (/), for example, {“id”:”1////2″}”, run the command

E/ssss: -1134829468:{“id”:”1////2″}

E/ssss: 1868680280:{“id”:”1////2″}

So no matter how many there are, they’re going to rotate

  • 2. What if it’s a /? Such as “{” id” : “1 \ / 2″}”

E/ssss: -1233361487:{“id”:”1/2″}

E/ssss: -1233361487:{“id”:”1/2″}

If you can see it, it won’t escape

  • “{“id”:”1\//2″}”

E/ssss: 420422874:{“id”:”1//2″}

E/ssss: 189688958:{“id”:”1//2″}

Therefore, we can conclude that if “/” is not escaped, otherwise “/” will be converted to “/”.

But!! JS

Using json.stringify () in JS does not automatically convert “/” to “/”, which causes problems because js and Java are not handled in the same way.

So how do we avoid that? Prevent inconsistency at both ends?

If you replace “/” with “/” after jsonObject.toString, you will not be able to do so because if you already have “/” in the string, you will be replaced with “/”.

A better way to do this is to initially convert any “/” in the string that is not a “/” to a “/”.

For example, after json.stringify (), execute STR = str.replace(///g, “\/”); Can be