用系统自带的org.json解析json数据有一个问题,最终拿到的数据键值对顺序不是json原来的顺序(有时候确实是有这种需求,比如:服务端调好了100个字段的数据,然后要按服务端返回的顺序显示)。因为org.json里面的JSONObject存入键值的是一个hashmap表,这个用过的朋友应该知道它是无序的。源码如下
/** * Creates a {@code JSONObject} with no name/value mappings. */ public JSONObject() { nameValuePairs = new HashMap<String, Object>(); }
那么知道问题点就简单了,源码是改不了,但是可以自己继承它,重新写一个解析器。这里,我重写了2个类,MJSONObject和MJSONTokener。
MJSONObject主要是存放json键值,里面用的是LinkedHashMap(有序)存放数据。MJSONTokener基本就是源码复制,然后简单改了下解析readobject方法。具体源码如下:
MJSONObject.java
import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; public class MJSONObject extends JSONObject { private LinkedHashMap<Object, Object> mHashMap; public MJSONObject() { super(); // TODO Auto-generated constructor stub mHashMap = new LinkedHashMap<Object, Object>(); } public MJSONObject(String json) { // TODO Auto-generated constructor stub mHashMap = new LinkedHashMap<Object, Object>(); try { MJSONTokener tokener = new MJSONTokener(json); Object object = tokener.nextValue(); if (object instanceof MJSONObject) { MJSONObject obj = (MJSONObject) object; this.mHashMap = obj.mHashMap; } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public JSONObject put(String name, boolean value) throws JSONException { // TODO Auto-generated method stub return put(name, value); } @Override public JSONObject put(String name, double value) throws JSONException { // TODO Auto-generated method stub return put(name, value); } @Override public JSONObject put(String name, int value) throws JSONException { // TODO Auto-generated method stub return put(name, value); } @Override public JSONObject put(String name, long value) throws JSONException { // TODO Auto-generated method stub return put(name, value); } @Override public JSONObject put(String key, Object value) throws JSONException { if (key == null) { throw new JSONException("Null key."); } if (value != null) { testValidity(value); mHashMap.put(key, value); } else { remove(key); } return this; } public Object remove(String key) { return mHashMap.remove(key); } @Override public boolean has(String name) { // TODO Auto-generated method stub return mHashMap.containsKey(name); } @Override public Object get(String name) throws JSONException { // TODO Auto-generated method stub return mHashMap.get(name); } @Override public Object opt(String name) { // TODO Auto-generated method stub return mHashMap.get(name); } @Override public JSONObject accumulate(String name, Object value) throws JSONException { // TODO Auto-generated method stub Object current = mHashMap.get(name); if (current == null) { return put(name, value); } if (current instanceof JSONArray) { JSONArray array = (JSONArray) current; array.put(value); } else { JSONArray array = new JSONArray(); array.put(current); array.put(value); mHashMap.put(name, array); } return this; } @Override public int length() { // TODO Auto-generated method stub return mHashMap.size(); } @Override public Iterator<?> keys() { // TODO Auto-generated method stub return mHashMap.keySet().iterator(); } static void testValidity(Object o) throws JSONException { if (o != null) { if (o instanceof Double) { if (((Double) o).isInfinite() || ((Double) o).isNaN()) { throw new JSONException( "JSON does not allow non-finite numbers."); } } else if (o instanceof Float) { if (((Float) o).isInfinite() || ((Float) o).isNaN()) { throw new JSONException( "JSON does not allow non-finite numbers."); } } } } public String toString() { try { Iterator<Object> keys = mHashMap.keySet().iterator(); StringBuffer sb = new StringBuffer("{"); while (keys.hasNext()) { if (sb.length() > 1) { sb.append(','); } Object o = keys.next(); sb.append(quote(o.toString())); sb.append(':'); sb.append(valueToString(mHashMap.get(o))); } sb.append('}'); return sb.toString(); } catch (Exception e) { return null; } } static String valueToString(Object value) throws JSONException { if (value == null || value.equals(null)) { return "null"; } if (value instanceof JSONStringer) { Object o; try { o = ((JSONStringer) value).toString(); } catch (Exception e) { throw new JSONException(e.getMessage()); } if (o instanceof String) { return (String) o; } throw new JSONException("Bad value from toJSONString: " + o); } if (value instanceof Number) { return numberToString((Number) value); } if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { return value.toString(); } if (value instanceof Map) { return new JSONObject((Map<?, ?>) value).toString(); } if (value instanceof Collection) { return new JSONArray((Collection<?>) value).toString(); } return quote(value.toString()); } }
MJSONTokener.java
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; // Note: this class was written without inspecting the non-free org.json sourcecode. /** * Parses a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) * encoded string into the corresponding object. Most clients of * this class will use only need the {@link #JSONTokener(String) constructor} * and {@link #nextValue} method. Example usage: <pre> * String json = "{" * + " \"query\": \"Pizza\", " * + " \"locations\": [ 94043, 90210 ] " * + "}"; * * JSONObject object = (JSONObject) new JSONTokener(json).nextValue(); * String query = object.getString("query"); * JSONArray locations = object.getJSONArray("locations");</pre> * * For best interoperability and performance use JSON that complies with * RFC 4627, such as that generated by {@link JSONStringer}. For legacy reasons * this parser is lenient, so a successful parse does not indicate that the * input string was valid JSON. All of the following syntax errors will be * ignored: * <ul> * <li>End of line comments starting with {@code //} or {@code #} and ending * with a newline character. * <li>C-style comments starting with {@code /*} and ending with * {@code *}{@code /}. Such comments may not be nested. * <li>Strings that are unquoted or {@code 'single quoted'}. * <li>Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. * <li>Octal integers prefixed with {@code 0}. * <li>Array elements separated by {@code ;}. * <li>Unnecessary array separators. These are interpreted as if null was the * omitted value. * <li>Key-value pairs separated by {@code =} or {@code =>}. * <li>Key-value pairs separated by {@code ;}. * </ul> * * Each tokener may be used to parse a single JSON string. Instances of this * class are not thread safe. Although this class is nonfinal, it was not * designed for inheritance and should not be subclassed. In particular, * self-use by overrideable methods is not specified. See <i>Effective Java</i> * Item 17, "Design and Document or inheritance or else prohibit it" for further * information. */ public class MJSONTokener { /** The input JSON. */ private final String in; /** * The index of the next character to be returned by {@link #next}. When * the input is exhausted, this equals the input's length. */ private int pos; /** * @param in JSON encoded string. Null is not permitted and will yield a * tokener that throws {@code NullPointerExceptions} when methods are * called. */ public MJSONTokener(String in) { // consume an optional byte order mark (BOM) if it exists if (in != null && in.startsWith("\ufeff")) { in = in.substring(1); } this.in = in; } /** * Returns the next value from the input. * * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, * Integer, Long, Double or {@link JSONObject#NULL}. * @throws JSONException if the input is malformed. */ public Object nextValue() throws JSONException { int c = nextCleanInternal(); switch (c) { case -1: throw syntaxError("End of input"); case '{': return readObject(); case '[': return readArray(); case '\'': case '"': return nextString((char) c); default: pos--; return readLiteral(); } } private int nextCleanInternal() throws JSONException { while (pos < in.length()) { int c = in.charAt(pos++); switch (c) { case '\t': case ' ': case '\n': case '\r': continue; case '/': if (pos == in.length()) { return c; } char peek = in.charAt(pos); switch (peek) { case '*': // skip a /* c-style comment */ pos++; int commentEnd = in.indexOf("*/", pos); if (commentEnd == -1) { throw syntaxError("Unterminated comment"); } pos = commentEnd + 2; continue; case '/': // skip a // end-of-line comment pos++; skipToEndOfLine(); continue; default: return c; } case '#': /* * Skip a # hash end-of-line comment. The JSON RFC doesn't * specify this behavior, but it's required to parse * existing documents. See http://b/2571423. */ skipToEndOfLine(); continue; default: return c; } } return -1; } /** * Advances the position until after the next newline character. If the line * is terminated by "\r\n", the '\n' must be consumed as whitespace by the * caller. */ private void skipToEndOfLine() { for (; pos < in.length(); pos++) { char c = in.charAt(pos); if (c == '\r' || c == '\n') { pos++; break; } } } /** * Returns the string up to but not including {@code quote}, unescaping any * character escape sequences encountered along the way. The opening quote * should have already been read. This consumes the closing quote, but does * not include it in the returned string. * * @param quote either ' or ". * @throws NumberFormatException if any unicode escape sequences are * malformed. */ public String nextString(char quote) throws JSONException { /* * For strings that are free of escape sequences, we can just extract * the result as a substring of the input. But if we encounter an escape * sequence, we need to use a StringBuilder to compose the result. */ StringBuilder builder = null; /* the index of the first character not yet appended to the builder. */ int start = pos; while (pos < in.length()) { int c = in.charAt(pos++); if (c == quote) { if (builder == null) { // a new string avoids leaking memory return new String(in.substring(start, pos - 1)); } else { builder.append(in, start, pos - 1); return builder.toString(); } } if (c == '\\') { if (pos == in.length()) { throw syntaxError("Unterminated escape sequence"); } if (builder == null) { builder = new StringBuilder(); } builder.append(in, start, pos - 1); builder.append(readEscapeCharacter()); start = pos; } } throw syntaxError("Unterminated string"); } /** * Unescapes the character identified by the character or characters that * immediately follow a backslash. The backslash '\' should have already * been read. This supports both unicode escapes "u000A" and two-character * escapes "\n". * * @throws NumberFormatException if any unicode escape sequences are * malformed. */ private char readEscapeCharacter() throws JSONException { char escaped = in.charAt(pos++); switch (escaped) { case 'u': if (pos + 4 > in.length()) { throw syntaxError("Unterminated escape sequence"); } String hex = in.substring(pos, pos + 4); pos += 4; return (char) Integer.parseInt(hex, 16); case 't': return '\t'; case 'b': return '\b'; case 'n': return '\n'; case 'r': return '\r'; case 'f': return '\f'; case '\'': case '"': case '\\': default: return escaped; } } /** * Reads a null, boolean, numeric or unquoted string literal value. Numeric * values will be returned as an Integer, Long, or Double, in that order of * preference. */ private Object readLiteral() throws JSONException { String literal = nextToInternal("{}[]/\\:,=;# \t\f"); if (literal.length() == 0) { throw syntaxError("Expected literal value"); } else if ("null".equalsIgnoreCase(literal)) { return JSONObject.NULL; } else if ("true".equalsIgnoreCase(literal)) { return Boolean.TRUE; } else if ("false".equalsIgnoreCase(literal)) { return Boolean.FALSE; } /* try to parse as an integral type... */ if (literal.indexOf('.') == -1) { int base = 10; String number = literal; if (number.startsWith("0x") || number.startsWith("0X")) { number = number.substring(2); base = 16; } else if (number.startsWith("0") && number.length() > 1) { number = number.substring(1); base = 8; } try { long longValue = Long.parseLong(number, base); if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { return (int) longValue; } else { return longValue; } } catch (NumberFormatException e) { /* * This only happens for integral numbers greater than * Long.MAX_VALUE, numbers in exponential form (5e-10) and * unquoted strings. Fall through to try floating point. */ } } /* ...next try to parse as a floating point... */ try { return Double.valueOf(literal); } catch (NumberFormatException ignored) { } /* ... finally give up. We have an unquoted string */ return new String(literal); // a new string avoids leaking memory } /** * Returns the string up to but not including any of the given characters or * a newline character. This does not consume the excluded character. */ private String nextToInternal(String excluded) { int start = pos; for (; pos < in.length(); pos++) { char c = in.charAt(pos); if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { return in.substring(start, pos); } } return in.substring(start); } /** * Reads a sequence of key/value pairs and the trailing closing brace '}' of * an object. The opening brace '{' should have already been read. */ private JSONObject readObject() throws JSONException { MJSONObject result = new MJSONObject(); /* Peek to see if this is the empty object. */ int first = nextCleanInternal(); if (first == '}') { return result; } else if (first != -1) { pos--; } while (true) { Object name = nextValue(); if (!(name instanceof String)) { if (name == null) { throw syntaxError("Names cannot be null"); } else { throw syntaxError("Names must be strings, but " + name + " is of type " + name.getClass().getName()); } } /* * Expect the name/value separator to be either a colon ':', an * equals sign '=', or an arrow "=>". The last two are bogus but we * include them because that's what the original implementation did. */ int separator = nextCleanInternal(); if (separator != ':' && separator != '=') { throw syntaxError("Expected ':' after " + name); } if (pos < in.length() && in.charAt(pos) == '>') { pos++; } result.put((String) name, nextValue()); switch (nextCleanInternal()) { case '}': return result; case ';': case ',': continue; default: throw syntaxError("Unterminated object"); } } } /** * Reads a sequence of values and the trailing closing brace ']' of an * array. The opening brace '[' should have already been read. Note that * "[]" yields an empty array, but "[,]" returns a two-element array * equivalent to "[null,null]". */ private JSONArray readArray() throws JSONException { JSONArray result = new JSONArray(); /* to cover input that ends with ",]". */ boolean hasTrailingSeparator = false; while (true) { switch (nextCleanInternal()) { case -1: throw syntaxError("Unterminated array"); case ']': if (hasTrailingSeparator) { result.put(null); } return result; case ',': case ';': /* A separator without a value first means "null". */ result.put(null); hasTrailingSeparator = true; continue; default: pos--; } result.put(nextValue()); switch (nextCleanInternal()) { case ']': return result; case ',': case ';': hasTrailingSeparator = true; continue; default: throw syntaxError("Unterminated array"); } } } /** * Returns an exception containing the given message plus the current * position and the entire input string. */ public JSONException syntaxError(String message) { return new JSONException(message + this); } /** * Returns the current position and the entire input string. */ @Override public String toString() { // consistent with the original implementation return " at character " + pos + " of " + in; } /* * Legacy APIs. * * None of the methods below are on the critical path of parsing JSON * documents. They exist only because they were exposed by the original * implementation and may be used by some clients. */ /** * Returns true until the input has been exhausted. */ public boolean more() { return pos < in.length(); } /** * Returns the next available character, or the null character '\0' if all * input has been exhausted. The return value of this method is ambiguous * for JSON strings that contain the character '\0'. */ public char next() { return pos < in.length() ? in.charAt(pos++) : '\0'; } /** * Returns the next available character if it equals {@code c}. Otherwise an * exception is thrown. */ public char next(char c) throws JSONException { char result = next(); if (result != c) { throw syntaxError("Expected " + c + " but was " + result); } return result; } /** * Returns the next character that is not whitespace and does not belong to * a comment. If the input is exhausted before such a character can be * found, the null character '\0' is returned. The return value of this * method is ambiguous for JSON strings that contain the character '\0'. */ public char nextClean() throws JSONException { int nextCleanInt = nextCleanInternal(); return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; } /** * Returns the next {@code length} characters of the input. * * The returned string shares its backing character array with this * tokener's input string. If a reference to the returned string may be held * indefinitely, you should use {@code new String(result)} to copy it first * to avoid memory leaks. * * @throws JSONException if the remaining input is not long enough to * satisfy this request. */ public String next(int length) throws JSONException { if (pos + length > in.length()) { throw syntaxError(length + " is out of bounds"); } String result = in.substring(pos, pos + length); pos += length; return result; } /** * Returns the {@link String#trim trimmed} string holding the characters up * to but not including the first of: * <ul> * <li>any character in {@code excluded} * <li>a newline character '\n' * <li>a carriage return '\r' * </ul> * * The returned string shares its backing character array with this * tokener's input string. If a reference to the returned string may be held * indefinitely, you should use {@code new String(result)} to copy it first * to avoid memory leaks. * * @return a possibly-empty string */ public String nextTo(String excluded) { if (excluded == null) { throw new NullPointerException("excluded == null"); } return nextToInternal(excluded).trim(); } /** * Equivalent to {@code nextTo(String.valueOf(excluded))}. */ public String nextTo(char excluded) { return nextToInternal(String.valueOf(excluded)).trim(); } /** * Advances past all input up to and including the next occurrence of * {@code thru}. If the remaining input doesn't contain {@code thru}, the * input is exhausted. */ public void skipPast(String thru) { int thruStart = in.indexOf(thru, pos); pos = thruStart == -1 ? in.length() : (thruStart + thru.length()); } /** * Advances past all input up to but not including the next occurrence of * {@code to}. If the remaining input doesn't contain {@code to}, the input * is unchanged. */ public char skipTo(char to) { int index = in.indexOf(to, pos); if (index != -1) { pos = index; return to; } else { return '\0'; } } /** * Unreads the most recent character of input. If no input characters have * been read, the input is unchanged. */ public void back() { if (--pos == -1) { pos = 0; } } /** * Returns the integer [0..15] value for the given hex character, or -1 * for non-hex input. * * @param hex a character in the ranges [0-9], [A-F] or [a-f]. Any other * character will yield a -1 result. */ public static int dehexchar(char hex) { if (hex >= '0' && hex <= '9') { return hex - '0'; } else if (hex >= 'A' && hex <= 'F') { return hex - 'A' + 10; } else if (hex >= 'a' && hex <= 'f') { return hex - 'a' + 10; } else { return -1; } } }
用法和官方一样,
MJSONObject root = new MJSONObject(jsonString); JSONArray data = root.getJSONArray("data");
收藏的用户(0) X
正在加载信息~
推荐阅读
最新回复 (0)
站点信息
- 文章2295
- 用户1336
- 访客10288276
每日一句
More dreams are lost to self-doubt than to failure.
因自我怀疑而失去的梦想比因失败而失去的更多。
因自我怀疑而失去的梦想比因失败而失去的更多。
新会员