package org.json.junit;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import org.json.*;
import org.junit.*;
/**
* Used in testing when a JSONString is needed
*/
class MyJsonString implements JSONString {
@Override
public String toJSONString() {
return "my string";
}
}
/**
* Used in testing when Bean behavior is needed
*/
interface MyBean {
public Integer getIntKey();
public Double getDoubleKey();
public String getStringKey();
public String getEscapeStringKey();
public Boolean isTrueKey();
public Boolean isFalseKey();
public StringReader getStringReaderKey();
};
/**
* Used in testing when a Bean containing big numbers is needed
*/
interface MyBigNumberBean {
public BigInteger getBigInteger();
public BigDecimal getBigDecimal();
}
/**
* JSONObject, along with JSONArray, are the central classes of the reference app.
* All of the other classes interact with them, and JSON functionality would
* otherwise be impossible.
*/
public class JSONObjectTest {
/**
* Need a class with some public data members for testing, so
* JSONObjectTest itself will be used for this purpose.
* TODO: Why not use MyBigNumberBean or MyBean?
*/
public Integer publicInt = 42;
public String publicString = "abc";
/**
* JSONObject built from a bean, but only using a null value.
* Nothing good is expected to happen.
* Expects NullPointerException
*/
@Test(expected=NullPointerException.class)
public void jsonObjectByNullBean() {
MyBean myBean = null;
new JSONObject(myBean);
}
/**
* A JSONObject can be created with no content
*/
@Test
public void emptyJsonObject() {
JSONObject jsonObject = new JSONObject();
assertTrue("jsonObject should be empty", jsonObject.length() == 0);
}
/**
* A JSONObject can be created from another JSONObject plus a list of names.
* In this test, some of the starting JSONObject keys are not in the
* names list.
*/
@Test
public void jsonObjectByNames() {
String str =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
"\"nullKey\":null,"+
"\"stringKey\":\"hello world!\","+
"\"escapeStringKey\":\"h\be\tllo w\u1234orld!\","+
"\"intKey\":42,"+
"\"doubleKey\":-23.45e67"+
"}";
String[] keys = {"falseKey", "stringKey", "nullKey", "doubleKey"};
String expectedStr =
"{"+
"\"falseKey\":false,"+
"\"nullKey\":null,"+
"\"stringKey\":\"hello world!\","+
"\"doubleKey\":-23.45e67"+
"}";
JSONObject jsonObject = new JSONObject(str);
JSONObject copyJsonObject = new JSONObject(jsonObject, keys);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(copyJsonObject, expectedJsonObject);
}
/**
* JSONObjects can be built from a Map.
* In this test the map is null.
* the JSONObject(JsonTokener) ctor is not tested directly since it already
* has full coverage from other tests.
*/
@Test
public void jsonObjectByNullMap() {
Map map = null;
JSONObject jsonObject = new JSONObject(map);
JSONObject expectedJsonObject = new JSONObject();
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* JSONObjects can be built from a Map.
* In this test all of the map entries are valid JSON types.
*/
@Test
public void jsonObjectByMap() {
String expectedStr =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
"\"stringKey\":\"hello world!\","+
"\"escapeStringKey\":\"h\be\tllo w\u1234orld!\","+
"\"intKey\":42,"+
"\"doubleKey\":-23.45e67"+
"}";
Map jsonMap = new HashMap();
jsonMap.put("trueKey", new Boolean(true));
jsonMap.put("falseKey", new Boolean(false));
jsonMap.put("stringKey", "hello world!");
jsonMap.put("escapeStringKey", "h\be\tllo w\u1234orld!");
jsonMap.put("intKey", new Long(42));
jsonMap.put("doubleKey", new Double(-23.45e67));
JSONObject jsonObject = new JSONObject(jsonMap);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* JSONObjects can be built from a Map.
* In this test the map entries are not valid JSON types.
* The actual conversion is kind of interesting.
*/
@Test
public void jsonObjectByMapWithUnsupportedValues() {
String expectedStr =
"{"+
"\"key1\":{},"+
"\"key2\":\"java.lang.Exception\""+
"}";
Map jsonMap = new HashMap();
// Just insert some random objects
jsonMap.put("key1", new CDL());
jsonMap.put("key2", new Exception());
JSONObject jsonObject = new JSONObject(jsonMap);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* JSONObjects can be built from a Map.
* In this test one of the map values is null
*/
@Test
public void jsonObjectByMapWithNullValue() {
String expectedStr =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
"\"stringKey\":\"hello world!\","+
"\"escapeStringKey\":\"h\be\tllo w\u1234orld!\","+
"\"intKey\":42,"+
"\"doubleKey\":-23.45e67"+
"}";
Map jsonMap = new HashMap();
jsonMap.put("trueKey", new Boolean(true));
jsonMap.put("falseKey", new Boolean(false));
jsonMap.put("stringKey", "hello world!");
jsonMap.put("nullKey", null);
jsonMap.put("escapeStringKey", "h\be\tllo w\u1234orld!");
jsonMap.put("intKey", new Long(42));
jsonMap.put("doubleKey", new Double(-23.45e67));
JSONObject jsonObject = new JSONObject(jsonMap);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* JSONObject built from a bean. In this case all but one of the
* bean getters return valid JSON types
*/
@Test
public void jsonObjectByBean() {
String expectedStr =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
"\"stringKey\":\"hello world!\","+
"\"escapeStringKey\":\"h\be\tllo w\u1234orld!\","+
"\"intKey\":42,"+
"\"doubleKey\":-23.45e7,"+
"\"stringReaderKey\":{},"+
"\"callbacks\":[{\"handler\":{}},{}]"+ // sorry, mockito artifact
"}";
/**
* Default access classes have to be mocked since JSONObject, which is
* not in the same package, cannot call MyBean methods by reflection.
*/
MyBean myBean = mock(MyBean.class);
when(myBean.getDoubleKey()).thenReturn(-23.45e7);
when(myBean.getIntKey()).thenReturn(42);
when(myBean.getStringKey()).thenReturn("hello world!");
when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!");
when(myBean.isTrueKey()).thenReturn(true);
when(myBean.isFalseKey()).thenReturn(false);
when(myBean.getStringReaderKey()).thenReturn(
new StringReader("") {
/**
* TODO: Need to understand why returning a string
* turns "this" into an empty JSONObject,
* but not overriding turns "this" into a string.
*/
@Override
public String toString(){
return "Whatever";
}
});
JSONObject jsonObject = new JSONObject(myBean);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* A bean is also an object. But in order to test the JSONObject
* ctor that takes an object and a list of names,
* this particular bean needs some public
* data members, which have been added to the class.
*/
@Test
public void jsonObjectByObjectAndNames() {
String expectedStr =
"{"+
"\"publicString\":\"abc\","+
"\"publicInt\":42"+
"}";
String[] keys = {"publicString", "publicInt"};
// just need a class that has public data members
JSONObjectTest jsonObjectTest = new JSONObjectTest();
JSONObject jsonObject = new JSONObject(jsonObjectTest, keys);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* Exercise the JSONObject from resource bundle functionality
*/
@Test
public void jsonObjectByResourceBundle() {
// TODO: how to improve resource bundle testing?
String expectedStr =
"{"+
"\"greetings\": {"+
"\"hello\":\"Hello, \","+
"\"world\":\"World!\""+
"},"+
"\"farewells\": {"+
"\"later\":\"Later, \","+
"\"gator\":\"Alligator!\""+
"}"+
"}";
JSONObject jsonObject = new
JSONObject("org.json.junit.StringsResourceBundle",
Locale.getDefault());
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* Exercise the JSONObject.accumulate() method
*/
@Test
public void jsonObjectAccumulate() {
// TODO: should include an unsupported object
String expectedStr =
"{"+
"\"myArray\": ["+
"true,"+
"false,"+
"\"hello world!\","+
"\"h\be\tllo w\u1234orld!\","+
"42,"+
"-23.45e7"+
"]"+
"}";
JSONObject jsonObject = new JSONObject();
jsonObject.accumulate("myArray", true);
jsonObject.accumulate("myArray", false);
jsonObject.accumulate("myArray", "hello world!");
jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!");
jsonObject.accumulate("myArray", 42);
jsonObject.accumulate("myArray", -23.45e7);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* Exercise the JSONObject append() functionality
*/
@Test
public void jsonObjectAppend() {
// TODO: should include an unsupported object
String expectedStr =
"{"+
"\"myArray\": ["+
"true,"+
"false,"+
"\"hello world!\","+
"\"h\be\tllo w\u1234orld!\","+
"42,"+
"-23.45e7"+
"]"+
"}";
JSONObject jsonObject = new JSONObject();
jsonObject.append("myArray", true);
jsonObject.append("myArray", false);
jsonObject.append("myArray", "hello world!");
jsonObject.append("myArray", "h\be\tllo w\u1234orld!");
jsonObject.append("myArray", 42);
jsonObject.append("myArray", -23.45e7);
JSONObject expectedJsonObject = new JSONObject(expectedStr);
Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject);
}
/**
* Exercise the JSONObject doubleToString() method
*/
@Test
public void jsonObjectDoubleToString() {
String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" };
Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67,
Double.NaN, Double.NEGATIVE_INFINITY };
for (int i = 0; i < expectedStrs.length; ++i) {
String actualStr = JSONObject.doubleToString(doubles[i]);
assertTrue("value expected ["+expectedStrs[i]+
"] found ["+actualStr+ "]",
expectedStrs[i].equals(actualStr));
}
}
/**
* Exercise some JSONObject get[type] and opt[type] methods
*/
@Test
public void jsonObjectValues() {
String str =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
"\"trueStrKey\":\"true\","+
"\"falseStrKey\":\"false\","+
"\"stringKey\":\"hello world!\","+
"\"intKey\":42,"+
"\"intStrKey\":\"43\","+
"\"longKey\":1234567890123456789,"+
"\"longStrKey\":\"987654321098765432\","+
"\"doubleKey\":-23.45e7,"+
"\"doubleStrKey\":\"00001.000\","+
"\"arrayKey\":[0,1,2],"+
"\"objectKey\":{\"myKey\":\"myVal\"}"+
"}";
JSONObject jsonObject = new JSONObject(str);
assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey"));
assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey"));
assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey"));
assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey"));
assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey"));
assertTrue("stringKey should be string",
jsonObject.getString("stringKey").equals("hello world!"));
assertTrue("doubleKey should be double",
jsonObject.getDouble("doubleKey") == -23.45e7);
assertTrue("doubleStrKey should be double",
jsonObject.getDouble("doubleStrKey") == 1);
assertTrue("opt doubleKey should be double",
jsonObject.optDouble("doubleKey") == -23.45e7);
assertTrue("opt doubleKey with Default should be double",
jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
assertTrue("intKey should be int",
jsonObject.optInt("intKey") == 42);
assertTrue("opt intKey should be int",
jsonObject.optInt("intKey", 0) == 42);
assertTrue("opt intKey with default should be int",
jsonObject.getInt("intKey") == 42);
assertTrue("intStrKey should be int",
jsonObject.getInt("intStrKey") == 43);
assertTrue("longKey should be long",
jsonObject.getLong("longKey") == 1234567890123456789L);
assertTrue("opt longKey should be long",
jsonObject.optLong("longKey") == 1234567890123456789L);
assertTrue("opt longKey with default should be long",
jsonObject.optLong("longKey", 0) == 1234567890123456789L);
assertTrue("longStrKey should be long",
jsonObject.getLong("longStrKey") == 987654321098765432L);
assertTrue("xKey should not exist",
jsonObject.isNull("xKey"));
assertTrue("stringKey should exist",
jsonObject.has("stringKey"));
assertTrue("opt stringKey should string",
jsonObject.optString("stringKey").equals("hello world!"));
assertTrue("opt stringKey with default should string",
jsonObject.optString("stringKey", "not found").equals("hello world!"));
JSONArray jsonArray = jsonObject.getJSONArray("arrayKey");
assertTrue("arrayKey should be JSONArray",
jsonArray.getInt(0) == 0 &&
jsonArray.getInt(1) == 1 &&
jsonArray.getInt(2) == 2);
jsonArray = jsonObject.optJSONArray("arrayKey");
assertTrue("opt arrayKey should be JSONArray",
jsonArray.getInt(0) == 0 &&
jsonArray.getInt(1) == 1 &&
jsonArray.getInt(2) == 2);
JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
assertTrue("objectKey should be JSONObject",
jsonObjectInner.get("myKey").equals("myVal"));
}
/**
* Check whether JSONObject handles large or high precision numbers correctly
*/
@Test
public void stringToValueNumbersTest() {
assertTrue( "0.2 should be a Double!",
JSONObject.stringToValue( "0.2" ) instanceof Double );
assertTrue( "Doubles should be Doubles, even when incorrectly converting floats!",
JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof Double );
/**
* This test documents a need for BigDecimal conversion.
*/
Object obj = JSONObject.stringToValue( "299792.457999999984" );
assertTrue( "evaluates to 299792.458 doubld instead of 299792.457999999984 BigDecimal!",
obj.equals(new Double(299792.458)) );
assertTrue( "1 should be an Integer!",
JSONObject.stringToValue( "1" ) instanceof Integer );
assertTrue( "Integer.MAX_VALUE should still be an Integer!",
JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer );
assertTrue( "Large integers should be a Long!",
JSONObject.stringToValue( new Long( Long.sum( Integer.MAX_VALUE, 1 ) ).toString() ) instanceof Long );
assertTrue( "Long.MAX_VALUE should still be an Integer!",
JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long );
String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
assertTrue( "Really large integers currently evaluate to string",
JSONObject.stringToValue(str).equals("9223372036854775808"));
}
/**
* This test documents numeric values which could be numerically
* handled as BigDecimal or BigInteger. It helps determine what outputs
* will change if those types are supported.
*/
@Test
public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
// Valid JSON Numbers, probably should return BigDecimal or BigInteger objects
String str =
"{"+
"\"numberWithDecimals\":299792.457999999984,"+
"\"largeNumber\":12345678901234567890,"+
"\"preciseNumber\":0.2000000000000000111,"+
"\"largeExponent\":-23.45e2327"+
"}";
JSONObject jsonObject = new JSONObject(str);
// Comes back as a double, but loses precision
assertTrue( "numberWithDecimals currently evaluates to double 299792.458",
jsonObject.get( "numberWithDecimals" ).equals( new Double( "299792.458" ) ) );
Object obj = jsonObject.get( "largeNumber" );
assertTrue("largeNumber currently evaluates to string",
"12345678901234567890".equals(obj));
// comes back as a double but loses precision
assertTrue( "preciseNumber currently evaluates to double 0.2",
jsonObject.get( "preciseNumber" ).equals(new Double(0.2)));
obj = jsonObject.get( "largeExponent" );
assertTrue("largeExponent should currently evaluates as a string",
"-23.45e2327".equals(obj));
}
/**
* This test documents how JSON-Java handles invalid numeric input.
*/
@Test
public void jsonInvalidNumberValues() {
// Number-notations supported by Java and invalid as JSON
String str =
"{"+
"\"hexNumber\":-0x123,"+
"\"tooManyZeros\":00,"+
"\"negativeInfinite\":-Infinity,"+
"\"negativeNaN\":-NaN,"+
"\"negativeFraction\":-.01,"+
"\"tooManyZerosFraction\":00.001,"+
"\"negativeHexFloat\":-0x1.fffp1,"+
"\"hexFloat\":0x1.0P-1074,"+
"\"floatIdentifier\":0.1f,"+
"\"doubleIdentifier\":0.1d"+
"}";
JSONObject jsonObject = new JSONObject(str);
Object obj;
obj = jsonObject.get( "hexNumber" );
assertFalse( "hexNumber must not be a number (should throw exception!?)",
obj instanceof Number );
assertTrue("hexNumber currently evaluates to string",
obj.equals("-0x123"));
assertTrue( "tooManyZeros currently evaluates to string",
jsonObject.get( "tooManyZeros" ).equals("00"));
obj = jsonObject.get("negativeInfinite");
assertTrue( "negativeInfinite currently evaluates to string",
obj.equals("-Infinity"));
obj = jsonObject.get("negativeNaN");
assertTrue( "negativeNaN currently evaluates to string",
obj.equals("-NaN"));
assertTrue( "negativeFraction currently evaluates to double -0.01",
jsonObject.get( "negativeFraction" ).equals(new Double(-0.01)));
assertTrue( "tooManyZerosFraction currently evaluates to double 0.001",
jsonObject.get( "tooManyZerosFraction" ).equals(new Double(0.001)));
assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875",
jsonObject.get( "negativeHexFloat" ).equals(new Double(-3.99951171875)));
assertTrue("hexFloat currently evaluates to double 4.9E-324",
jsonObject.get("hexFloat").equals(new Double(4.9E-324)));
assertTrue("floatIdentifier currently evaluates to double 0.1",
jsonObject.get("floatIdentifier").equals(new Double(0.1)));
assertTrue("doubleIdentifier currently evaluates to double 0.1",
jsonObject.get("doubleIdentifier").equals(new Double(0.1)));
}
/**
* Tests how JSONObject get[type] handles incorrect types
*/
@Test
public void jsonObjectNonAndWrongValues() {
String str =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
"\"trueStrKey\":\"true\","+
"\"falseStrKey\":\"false\","+
"\"stringKey\":\"hello world!\","+
"\"intKey\":42,"+
"\"intStrKey\":\"43\","+
"\"longKey\":1234567890123456789,"+
"\"longStrKey\":\"987654321098765432\","+
"\"doubleKey\":-23.45e7,"+
"\"doubleStrKey\":\"00001.000\","+
"\"arrayKey\":[0,1,2],"+
"\"objectKey\":{\"myKey\":\"myVal\"}"+
"}";
JSONObject jsonObject = new JSONObject(str);
try {
jsonObject.getBoolean("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("expecting an exception message",
"JSONObject[\"nonKey\"] not found.".equals(e.getMessage()));
}
try {
jsonObject.getBoolean("stringKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"stringKey\"] is not a Boolean.".
equals(e.getMessage()));
}
try {
jsonObject.getString("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"nonKey\"] not found.".
equals(e.getMessage()));
}
try {
jsonObject.getString("trueKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"trueKey\"] not a string.".
equals(e.getMessage()));
}
try {
jsonObject.getDouble("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"nonKey\"] not found.".
equals(e.getMessage()));
}
try {
jsonObject.getDouble("stringKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"stringKey\"] is not a number.".
equals(e.getMessage()));
}
try {
jsonObject.getInt("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"nonKey\"] not found.".
equals(e.getMessage()));
}
try {
jsonObject.getInt("stringKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"stringKey\"] is not an int.".
equals(e.getMessage()));
}
try {
jsonObject.getLong("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"nonKey\"] not found.".
equals(e.getMessage()));
}
try {
jsonObject.getLong("stringKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"stringKey\"] is not a long.".
equals(e.getMessage()));
}
try {
jsonObject.getJSONArray("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"nonKey\"] not found.".
equals(e.getMessage()));
}
try {
jsonObject.getJSONArray("stringKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"stringKey\"] is not a JSONArray.".
equals(e.getMessage()));
}
try {
jsonObject.getJSONObject("nonKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"nonKey\"] not found.".
equals(e.getMessage()));
}
try {
jsonObject.getJSONObject("stringKey");
assertTrue("Expected an exception", false);
} catch (JSONException e) {
assertTrue("Expecting an exception message",
"JSONObject[\"stringKey\"] is not a JSONObject.".
equals(e.getMessage()));
}
}
/**
* This test documents an unexpected numeric behavior.
* A double that ends with .0 is parsed, serialized, then
* parsed again. On the second parse, it has become an int.
*/
@Test
public void unexpectedDoubleToIntConversion() {
String key30 = "key30";
String key31 = "key31";
JSONObject jsonObject = new JSONObject();
jsonObject.put(key30, new Double(3.0));
jsonObject.put(key31, new Double(3.1));
assertTrue("3.0 should remain a double",
jsonObject.getDouble(key30) == 3);
assertTrue("3.1 should remain a double",
jsonObject.getDouble(key31) == 3.1);
// turns 3.0 into 3.
String serializedString = jsonObject.toString();
JSONObject deserialized = new JSONObject(serializedString);
assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer);
assertTrue("3.0 can still be interpreted as a double",
deserialized.getDouble(key30) == 3.0);
assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1);
}
/**
* Document behaviors of big numbers. Includes both JSONObject
* and JSONArray tests
*/
@Test
public void bigNumberOperations() {
/**
* JSONObject tries to parse BigInteger as a bean, but it only has
* one getter, getLowestBitSet(). The value is lost and an unhelpful
* value is stored. This should be fixed.
*/
BigInteger bigInteger = new BigInteger("123456789012345678901234567890");
JSONObject jsonObject = new JSONObject(bigInteger);
Object obj = jsonObject.get("lowestSetBit");
assertTrue("JSONObject only has 1 value", jsonObject.length() == 1);
assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet",
obj instanceof Integer);
assertTrue("this bigInteger lowestBitSet happens to be 1",
obj.equals(1));
/**
* JSONObject tries to parse BigDecimal as a bean, but it has
* no getters, The value is lost and no value is stored.
* This should be fixed.
*/
BigDecimal bigDecimal = new BigDecimal(
"123456789012345678901234567890.12345678901234567890123456789");
jsonObject = new JSONObject(bigDecimal);
assertTrue("large bigDecimal is not stored", jsonObject.length() == 0);
/**
* JSONObject put(String, Object) method stores and serializes
* bigInt and bigDec correctly. Nothing needs to change.
* TODO: New methods
* get|optBigInteger|BigDecimal() should work like other supported
* objects. Uncomment the get/opt methods after JSONObject is updated.
*/
jsonObject = new JSONObject();
jsonObject.put("bigInt", bigInteger);
assertTrue("jsonObject.put() handles bigInt correctly",
jsonObject.get("bigInt").equals(bigInteger));
// assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
// jsonObject.getBigInteger("bigInt").equals(bigInteger));
// assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
// jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
assertTrue("jsonObject serializes bigInt correctly",
jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
jsonObject = new JSONObject();
jsonObject.put("bigDec", bigDecimal);
assertTrue("jsonObject.put() handles bigDec correctly",
jsonObject.get("bigDec").equals(bigDecimal));
// assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
// jsonObject.getBigDecimal("bigDec").equals(bigDecimal));
// assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
// jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
assertTrue("jsonObject serializes bigDec correctly",
jsonObject.toString().equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
JSONArray jsonArray = new JSONArray();
/**
* JSONObject.numberToString() works correctly, nothing to change.
*/
String str = JSONObject.numberToString(bigInteger);
assertTrue("numberToString() handles bigInteger correctly",
str.equals("123456789012345678901234567890"));
str = JSONObject.numberToString(bigDecimal);
assertTrue("numberToString() handles bigDecimal correctly",
str.equals("123456789012345678901234567890.12345678901234567890123456789"));
/**
* JSONObject.stringToValue() turns bigInt into an accurate string,
* and rounds bigDec. This incorrect, but users may have come to
* expect this behavior. Change would be marginally better, but
* might inconvenience users.
*/
obj = JSONObject.stringToValue(bigInteger.toString());
assertTrue("stringToValue() turns bigInteger string into string",
obj instanceof String);
obj = JSONObject.stringToValue(bigDecimal.toString());
assertTrue("stringToValue() changes bigDecimal string",
!obj.toString().equals(bigDecimal.toString()));
/**
* wrap() vs put() big number behavior is now the same.
*/
// bigInt map ctor
Map map = new HashMap();
map.put("bigInt", bigInteger);
jsonObject = new JSONObject(map);
String actualFromMapStr = jsonObject.toString();
assertTrue("bigInt in map (or array or bean) is a string",
actualFromMapStr.equals(
"{\"bigInt\":123456789012345678901234567890}"));
// bigInt put
jsonObject = new JSONObject();
jsonObject.put("bigInt", bigInteger);
String actualFromPutStr = jsonObject.toString();
assertTrue("bigInt from put is a number",
actualFromPutStr.equals(
"{\"bigInt\":123456789012345678901234567890}"));
// bigDec map ctor
map = new HashMap();
map.put("bigDec", bigDecimal);
jsonObject = new JSONObject(map);
actualFromMapStr = jsonObject.toString();
assertTrue("bigDec in map (or array or bean) is a bigDec",
actualFromMapStr.equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
// bigDec put
jsonObject = new JSONObject();
jsonObject.put("bigDec", bigDecimal);
actualFromPutStr = jsonObject.toString();
assertTrue("bigDec from put is a number",
actualFromPutStr.equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
// bigInt,bigDec put
jsonArray = new JSONArray();
jsonArray.put(bigInteger);
jsonArray.put(bigDecimal);
actualFromPutStr = jsonArray.toString();
assertTrue("bigInt, bigDec from put is a number",
actualFromPutStr.equals(
"[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
assertTrue("getBigInt is bigInt", jsonArray.getBigInteger(0).equals(bigInteger));
assertTrue("getBigDec is bigDec", jsonArray.getBigDecimal(1).equals(bigDecimal));
assertTrue("optBigInt is bigInt", jsonArray.optBigInteger(0, BigInteger.ONE).equals(bigInteger));
assertTrue("optBigDec is bigDec", jsonArray.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal));
jsonArray.put(Boolean.TRUE);
try {
jsonArray.getBigInteger(2);
assertTrue("should not be able to get big int", false);
} catch (Exception ignored) {}
try {
jsonArray.getBigDecimal(2);
assertTrue("should not be able to get big dec", false);
} catch (Exception ignored) {}
assertTrue("optBigInt is default", jsonArray.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE));
assertTrue("optBigDec is default", jsonArray.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE));
// bigInt,bigDec list ctor
List