diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 68d302d56..42c5c31a2 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -143,7 +143,7 @@ public String toString() { /** * The map where the JSONObject's properties are kept. */ - private final Map map; + protected final Map map; public Class getMapType() { return map.getClass(); @@ -191,16 +191,16 @@ public JSONObject(JSONObject jo, String ... names) { } /** - * Construct a JSONObject from a JSONTokener. - * - * @param x - * A JSONTokener object containing the source string. - * @throws JSONException - * If there is a syntax error in the source string or a - * duplicated key. - */ + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ public JSONObject(JSONTokener x) throws JSONException { - this(); + this(x.getMapImplementation()); char c; String key; @@ -357,6 +357,19 @@ public JSONObject(Object bean) { this.populateMap(bean); } + protected JSONObject(Class mapImplementation) { + if( mapImplementation == null ) + // Use the default implementation. + this.map = new HashMap(); + else { + try { + this.map = mapImplementation.getConstructor().newInstance(); + } catch (Exception e) { + throw new JSONException("Error on instantiating default constructor of map implementation from class '" + mapImplementation + "'"); + } + } + } + private JSONObject(Object bean, Set objectsRecord) { this(); this.populateMap(bean, objectsRecord); diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java index 8c98c7798..f837c73a4 100644 --- a/src/main/java/org/json/JSONTokener.java +++ b/src/main/java/org/json/JSONTokener.java @@ -6,6 +6,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; +import java.util.Map; /* Public Domain. @@ -36,6 +37,8 @@ public class JSONTokener { /** the number of characters read in the previous line. */ private long characterPreviousLine; + /** custom map implementation to use. */ + private Class mapImplementation = null; /** * Construct a JSONTokener from a Reader. The caller must close the Reader. @@ -491,6 +494,15 @@ public char skipTo(char to) throws JSONException { return c; } + public Class getMapImplementation() { + return mapImplementation; + } + + public JSONTokener setMapImplementation(Class mapImplementation) { + this.mapImplementation = mapImplementation; + return this; + } + /** * Make a JSONException to signal a syntax error. * diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index ea0cec39c..07a183ed7 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -3509,4 +3509,27 @@ public String toJSONString() { .put("b", 2); assertFalse(jo1.similar(jo3)); } + + + @Test + public void jsonObjectOrdered() { + class OrderedJSONObject extends JSONObject { + public OrderedJSONObject() { + super( LinkedHashMap.class ); + } + } + + JSONObject jsonObject = new OrderedJSONObject(); + for (int i = 0; i < 100; i++) + jsonObject.put(String.valueOf(i), i); + + // validate JSON + assertEquals("expected 100 items",100, jsonObject.length()); + + int i = 0; + for( String key : jsonObject.keySet() ){ + assertEquals("expected "+i+" as key",i, Integer.parseInt(key)); + ++i; + } + } } diff --git a/src/test/java/org/json/junit/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java index da716b896..5b540e155 100644 --- a/src/test/java/org/json/junit/JSONTokenerTest.java +++ b/src/test/java/org/json/junit/JSONTokenerTest.java @@ -4,10 +4,11 @@ Public Domain. */ -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.junit.Test; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -16,11 +17,12 @@ import java.io.Reader; import java.io.StringReader; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONTokener; -import org.junit.Test; +import java.util.LinkedHashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Test specific to the {@link org.json.JSONTokener} class. @@ -313,4 +315,28 @@ public void testNextBackComboWithNewLines() { assertEquals(0, t2.next()); assertFalse(t2.more()); } + + @Test + public void jsonObjectOrdered() { + class OrderedJSONObject extends JSONObject { + public OrderedJSONObject() { + super( LinkedHashMap.class ); + } + } + + JSONObject jsonObject = new OrderedJSONObject(); + for (int i = 0; i < 100; i++) + jsonObject.put(String.valueOf(i), i); + + JSONObject orderedObject = new JSONObject( new JSONTokener(jsonObject.toString()).setMapImplementation( LinkedHashMap.class ) ); + + // validate JSON + assertEquals("expected 100 items",100, orderedObject.length()); + + int i = 0; + for( String key : orderedObject.keySet() ){ + assertEquals("expected "+i+" as key",i, Integer.parseInt(key)); + ++i; + } + } }