/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2006-2024 the original author or authors.
 *
 * 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
 *
 *     https://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.
 */
package org.kordamp.json;

import junit.framework.TestCase;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.kordamp.ezmorph.MorphUtils;
import org.kordamp.ezmorph.bean.MorphDynaBean;
import org.kordamp.ezmorph.bean.MorphDynaClass;
import org.kordamp.ezmorph.test.ArrayAssertions;
import org.kordamp.json.processors.DefaultValueProcessor;
import org.kordamp.json.processors.DefaultValueProcessorMatcher;
import org.kordamp.json.processors.PropertyNameProcessor;
import org.kordamp.json.sample.BeanA;
import org.kordamp.json.sample.BeanB;
import org.kordamp.json.sample.BeanC;
import org.kordamp.json.sample.BeanFoo;
import org.kordamp.json.sample.BeanWithFunc;
import org.kordamp.json.sample.ChildBean;
import org.kordamp.json.sample.ClassBean;
import org.kordamp.json.sample.EmptyBean;
import org.kordamp.json.sample.JavaIdentifierBean;
import org.kordamp.json.sample.ListingBean;
import org.kordamp.json.sample.MappingBean;
import org.kordamp.json.sample.NumberBean;
import org.kordamp.json.sample.ObjectBean;
import org.kordamp.json.sample.ObjectJSONStringBean;
import org.kordamp.json.sample.ParentBean;
import org.kordamp.json.sample.PrimitiveBean;
import org.kordamp.json.sample.PropertyBean;
import org.kordamp.json.sample.SetBean;
import org.kordamp.json.sample.TransientBean;
import org.kordamp.json.sample.ValueBean;
import org.kordamp.json.test.JSONAssert;
import org.kordamp.json.util.CycleDetectionStrategy;
import org.kordamp.json.util.JSONTokener;
import org.kordamp.json.util.JSONUtils;
import org.kordamp.json.util.JavaIdentifierTransformer;
import org.kordamp.json.util.PropertyExclusionClassMatcher;
import org.kordamp.json.util.PropertyFilter;
import org.kordamp.json.util.PropertySetStrategy;

import java.io.Serializable;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Andres Almiray
 */
public class TestJSONObject extends TestCase {
    private JsonConfig jsonConfig;

    public TestJSONObject(String testName) {
        super(testName);
    }

    public static void main(String[] args) {
        junit.textui.TestRunner.run(TestJSONObject.class);
    }

    public void testAccumulate() {
        JSONObject json = new JSONObject();
        json.accumulate("key", "1");
        Assertions.assertEquals(1, json.getInt("key"));
        json.accumulate("key", "2");
        Assertions.assertEquals(JSONArray.fromObject("['1','2']"), json.getJSONArray("key"));
        json.accumulate("key", "3");
        Assertions.assertEquals(JSONArray.fromObject("['1','2','3']"), json.getJSONArray("key"));
    }

    public void testAccumulate__nullObject() {
        try {
            new JSONObject(true).accumulate("key", "value");
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // ok
        }
    }

    public void testConstructor_Object__nullJSONObject() {
        JSONObject jsonObject = JSONObject.fromObject((JSONObject) null);
        assertTrue(jsonObject.isNullObject());
    }

    public void testConstructor_Object_String_Array__nullObject() {
        jsonConfig.setExcludes(new String[]{"bool", "integer"});
        JSONObject jsonObject = JSONObject.fromObject((Object) null, jsonConfig);
        assertTrue(jsonObject.isNullObject());
    }

    public void testCycleDetection_beans_noprop() {
        jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.NOPROP);
        ParentBean parent = new ParentBean();
        parent.setChild(new ChildBean());

        JSONObject actual = JSONObject.fromObject(parent, jsonConfig);
        JSONObject expected = new JSONObject().element("value", 0)
            .element("child", new JSONObject().element("value", 0));
        Assertions.assertEquals(expected, actual);
    }

    public void testCycleDetection_beans_null() {
        jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
        ParentBean parent = new ParentBean();
        parent.setChild(new ChildBean());

        JSONObject actual = JSONObject.fromObject(parent, jsonConfig);
        JSONObject expected = new JSONObject().element("value", 0)
            .element("child", new JSONObject().element("value", 0)
                .element("parent", new JSONObject(true)));
        Assertions.assertEquals(expected, actual);
    }

    public void testCycleDetection_beans_strict() {
        ParentBean parent = new ParentBean();
        parent.setChild(new ChildBean());
        try {
            JSONObject.fromObject(parent);
            fail("A JSONException was expected");
        } catch (JSONException expected) {
            assertTrue(expected.getMessage()
                .endsWith("There is a cycle in the hierarchy!"));
        }
    }

    public void testDiscard() {
        JSONObject jsonObject = new JSONObject().element("int", "1")
            .element("long", "1")
            .element("boolean", "true")
            .element("string", "string")
            .element("func", "function(){ return this; }")
            .element("array", "[1,2,3]");
        assertEquals(6, jsonObject.size());
        jsonObject.discard("int")
            .discard("func");
        assertEquals(4, jsonObject.size());
        assertFalse(jsonObject.has("int"));
        assertFalse(jsonObject.has("func"));
    }

    public void testElement__duplicateProperty() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("duplicated", "json1");
        jsonObject.element("duplicated", "json2");
        Object o = jsonObject.get("duplicated");
        assertFalse(o instanceof JSONArray);
        assertEquals("json2", o);
    }

    public void testElement__duplicateProperty_2() {
        JSONObject jsonObject = JSONObject.fromObject("{'duplicated':'json1','duplicated':'json2'}");
        Object o = jsonObject.get("duplicated");
        assertTrue(o instanceof JSONArray);
        assertEquals(new JSONArray().element("json1")
            .element("json2"), o);
    }

    public void testElement_Bean() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("bean", new ObjectBean());
        JSONObject actual = jsonObject.getJSONObject("bean");
        Assertions.assertTrue(!actual.has("class"));
    }

    public void testElement_Bean_exclusions() {
        JSONObject jsonObject = new JSONObject();
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        jsonObject.element("bean", new ObjectBean(), jsonConfig);
        JSONObject actual = jsonObject.getJSONObject("bean");
        Assertions.assertTrue(!actual.has("class"));
        Assertions.assertTrue(!actual.has("pexcluded"));
    }

    public void testElement_Bean_exclusions_ignoreDefault() {
        JSONObject jsonObject = new JSONObject();
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        jsonConfig.setIgnoreDefaultExcludes(true);
        jsonObject.element("bean", new ObjectBean(), jsonConfig);
        JSONObject actual = jsonObject.getJSONObject("bean");
        Assertions.assertTrue(actual.has("class"));
        Assertions.assertTrue(!actual.has("pexcluded"));
    }

    public void testElement_boolean() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("bool", true);
        assertTrue(jsonObject.getBoolean("bool"));
    }

    public void testElement_Boolean() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("bool", Boolean.TRUE);
        Assertions.assertTrue(jsonObject.getBoolean("bool"));
    }

    public void testElement_Class() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("class", Object.class);
        assertEquals("java.lang.Object", jsonObject.get("class"));
    }

    public void testElement_Collection() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("list", Collections.EMPTY_LIST);
        Assertions.assertEquals(new JSONArray(), jsonObject.getJSONArray("list"));
    }

    public void testElement_Collection2() {
        List list = new ArrayList();
        list.add(new ObjectBean());
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("list", list);
        JSONObject actual = jsonObject.getJSONArray("list")
            .getJSONObject(0);
        Assertions.assertTrue(!actual.has("class"));
    }

    public void testElement_Collection2_exclusions() {
        List list = new ArrayList();
        list.add(new ObjectBean());
        JSONObject jsonObject = new JSONObject();
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        jsonObject.element("list", list, jsonConfig);
        JSONObject actual = jsonObject.getJSONArray("list")
            .getJSONObject(0);
        Assertions.assertTrue(!actual.has("class"));
        Assertions.assertTrue(!actual.has("pexcluded"));
    }

    public void testElement_Collection2_exclusions_ignoreDefault() {
        List list = new ArrayList();
        list.add(new ObjectBean());
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        jsonConfig.setIgnoreDefaultExcludes(true);
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("list", list, jsonConfig);
        JSONObject actual = jsonObject.getJSONArray("list")
            .getJSONObject(0);
        Assertions.assertTrue(actual.has("class"));
        Assertions.assertTrue(!actual.has("pexcluded"));
    }

    public void testElement_double() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("double", 1d);
        assertEquals(1d, jsonObject.getDouble("double"), 0d);
    }

    public void testElement_int() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("int", 1);
        assertEquals(1, jsonObject.getInt("int"));
    }

    public void testElement_JSON() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("null", JSONNull.getInstance());
        Assertions.assertEquals(JSONNull.getInstance(), jsonObject.get("null"));
    }

    public void testElement_JSONFunction() {
        JSONObject jsonObject = new JSONObject();
        JSONFunction f = new JSONFunction("return this;");
        jsonObject.element("func", f);
        Assertions.assertEquals(f, (JSONFunction) jsonObject.get("func"));
    }

    public void testElement_JSONString() {
        JSONObject jsonObject = new JSONObject();
        ObjectJSONStringBean bean = new ObjectJSONStringBean();
        bean.setName("json");
        jsonObject.element("bean", bean);
        Assertions.assertEquals(JSONObject.fromObject(bean), jsonObject.getJSONObject("bean"));
    }

    public void testElement_JSONTokener() {
        JSONObject jsonObject = new JSONObject();
        JSONTokener tok = new JSONTokener("{'name':'json'}");
        jsonObject.element("obj", tok);
        tok.reset();
        Assertions.assertEquals(JSONObject.fromObject(tok), jsonObject.getJSONObject("obj"));
    }

    public void testElement_long() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("long", 1L);
        assertEquals(1L, jsonObject.getLong("long"));
    }

    public void testElement_Map() {
        Map map = new HashMap();
        map.put("name", "json");
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("map", map);
        Assertions.assertEquals(JSONObject.fromObject(map), jsonObject.getJSONObject("map"));
    }

    public void testElement_Map2() {
        Map map = new HashMap();
        map.put("name", "json");
        map.put("class", "java.lang.Object");
        map.put("excluded", "excluded");
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("map", map);
        JSONObject actual = jsonObject.getJSONObject("map");
        Assertions.assertTrue(!actual.has("class"));
    }

    public void testElement_Map2_exclusions() {
        Map map = new HashMap();
        map.put("name", "json");
        map.put("class", "java.lang.Object");
        map.put("pexcluded", "excluded");
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("map", map, jsonConfig);
        JSONObject actual = jsonObject.getJSONObject("map");
        Assertions.assertTrue(!actual.has("class"));
        Assertions.assertTrue(!actual.has("pexcluded"));
    }

    public void testElement_Map2_exclusions_ignoreDefault() {
        Map map = new HashMap();
        map.put("name", "json");
        map.put("class", "java.lang.Object");
        map.put("pexcluded", "excluded");
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        jsonConfig.setIgnoreDefaultExcludes(true);
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("map", map, jsonConfig);
        JSONObject actual = jsonObject.getJSONObject("map");
        Assertions.assertTrue(actual.has("class"));
        Assertions.assertTrue(!actual.has("pexcluded"));
    }

    public void testElement_null_key() {
        try {
            new JSONObject().element(null, "value");
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // ok
        }
    }

    public void testElement_Number() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("num", new Double(2));
        Assertions.assertEquals(new Double(2).doubleValue(), jsonObject.getDouble("num"), 0d);
    }

    public void testElement_Object() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("bean", new BeanA());
        Assertions.assertEquals(JSONObject.fromObject(new BeanA()),
            jsonObject.getJSONObject("bean"));
    }

    public void testElement_String() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("str", "json");
        Assertions.assertEquals("json", jsonObject.getString("str"));
    }

    public void testElement_String_JSON() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("str", "[]");
        Assertions.assertEquals(new JSONArray().toString(), jsonObject.getString("str"));
    }

    public void testElement_String_null() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.element("str", (String) null);
        // special case, if value null, there is no value associated to key
        try {
            jsonObject.getString("str");
            fail("Should have thrown a JSONException");
        } catch (JSONException expected) {
            // ok
        }
    }

    public void testFromBean_array() {
        try {
            JSONObject.fromObject(new ArrayList());
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // OK
        }

        try {
            JSONObject.fromObject(new String[]{"json"});
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // OK
        }
    }

    public void testFromBean_ClassBean() {
        ClassBean classBean = new ClassBean();
        classBean.setKlass(Object.class);
        JSONObject json = JSONObject.fromObject(classBean);
        assertEquals("java.lang.Object", json.get("klass"));
    }

    public void testFromBean_DynaBean() throws Exception {
        JSONObject json = JSONObject.fromObject(createDynaBean());
        assertEquals("json", json.getString("name"));
        Assertions.assertEquals("[1,2]", json.getString("str"));
        Assertions.assertEquals(JSONObject.fromObject("{'id':'1'}"), json.getJSONObject("json"));
        Assertions.assertEquals(JSONObject.fromObject("{'name':''}"),
            json.getJSONObject("jsonstr"));
        Assertions.assertEquals("function(){ return this; }", (JSONFunction) json.get("func"));
    }

    public void testFromBean_JSONObject() {
        JSONObject json = new JSONObject();
        json.element("name", "json");
        Assertions.assertEquals(json, JSONObject.fromObject(json));
    }

    public void testFromBean_JSONString() {
        ObjectJSONStringBean bean = new ObjectJSONStringBean();
        bean.setId(1);
        bean.setName("json");
        JSONObject json = JSONObject.fromObject(bean);
        assertEquals("json", json.getString("name"));
        assertTrue(!json.has("id"));
    }

    public void testFromBean_JSONTokener() {
        JSONTokener jsonTokener = new JSONTokener("{\"string\":\"json\"}");
        JSONObject json = JSONObject.fromObject(jsonTokener);
        assertEquals("json", json.getString("string"));
    }

    public void testFromBean_Map() {
        Map map = new HashMap();
        map.put("bool", Boolean.TRUE);
        map.put("integer", new Integer(42));
        map.put("string", "json");

        JSONObject json = JSONObject.fromObject(map);
        assertEquals(true, json.getBoolean("bool"));
        assertEquals(42, json.getInt("integer"));
        assertEquals("json", json.getString("string"));
    }

    public void testFromBean_noReadMethod() {
        JSONObject json = JSONObject.fromObject(new PropertyBean());
        assertTrue(json.has("propertyWithNoWriteMethod"));
        assertTrue(!json.has("propertyWithNoReadMethod"));
    }

    public void testFromBean_null() {
        JSONObject json = JSONObject.fromObject(null);
        assertTrue(json.isNullObject());
        assertEquals(JSONNull.getInstance()
            .toString(), json.toString());
    }

    public void testFromBean_String() {
        JSONObject json = JSONObject.fromObject("{\"string\":\"json\"}");
        assertEquals("json", json.getString("string"));
    }

    public void testFromBean_use_wrappers() {
        JSONObject json = JSONObject.fromObject(Boolean.TRUE);
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Byte(Byte.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Short(Short.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Integer(Integer.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Long(Long.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Float(Float.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Double(Double.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Character('A'));
        assertTrue(json.isEmpty());
    }

    public void testFromBeanWithJsonPropertyNameProcessor() {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerJsonPropertyNameProcessor(BeanA.class, new PrefixerPropertyNameProcessor("json"));
        JSONObject jsonObject = JSONObject.fromObject(new BeanA(), jsonConfig);
        assertNotNull(jsonObject);
        assertEquals(3, jsonObject.names().size());
        assertTrue(jsonObject.has("jsonbool"));
        assertTrue(jsonObject.has("jsonstring"));
        assertTrue(jsonObject.has("jsoninteger"));
    }

    public void testFromDynaBean_full() throws Exception {
        Map properties = new HashMap();
        properties.put("string", String.class);
        properties.put("number", Integer.class);
        properties.put("array", Object[].class);
        properties.put("list", List.class);
        properties.put("func", JSONFunction.class);
        properties.put("boolean", Boolean.class);
        properties.put("bean", BeanA.class);
        MorphDynaClass dynaClass = new MorphDynaClass("JSON", MorphDynaBean.class, properties);
        MorphDynaBean dynaBean = (MorphDynaBean) dynaClass.newInstance();
        dynaBean.setDynaBeanClass(dynaClass);
        dynaBean.set("string", "json");
        dynaBean.set("number", new Double(2));
        dynaBean.set("array", new Integer[]{new Integer(1), new Integer(2)});
        dynaBean.set("list", new ArrayList());
        dynaBean.set("func", new JSONFunction(new String[]{"a"}, "return a;"));
        dynaBean.set("boolean", Boolean.TRUE);
        dynaBean.set("bean", new BeanA());

        JSONObject jsonObject = JSONObject.fromObject(dynaBean);
        assertEquals("json", jsonObject.get("string"));
        assertEquals(new Double(2), jsonObject.get("number"));
        assertEquals(Boolean.TRUE, jsonObject.get("boolean"));
        Assertions.assertEquals("function(a){ return a; }", (JSONFunction) jsonObject.get("func"));
    }

    public void testFromDynaBean_null() {
        JSONObject jsonObject = JSONObject.fromObject(null);
        assertTrue(jsonObject.isNullObject());
    }

    public void testFromJSONTokener() {
        JSONTokener jsonTokener = new JSONTokener("{\"string\":\"json\"}");
        JSONObject json = JSONObject.fromObject(jsonTokener);
        assertEquals("json", json.getString("string"));
    }

    public void testFromMap_nested_null_object() {
        Map map = new HashMap();
        map.put("nested", null);
        map.put("string", "json");

        JSONObject json = JSONObject.fromObject(map);
        assertEquals("json", json.getString("string"));
        Object nested = json.get("nested");
        assertTrue(JSONUtils.isNull(nested));
    }

    public void testFromMap_null_Map() {
        JSONObject json = JSONObject.fromObject(null);
        assertTrue(json.isNullObject());
        assertEquals(JSONNull.getInstance()
            .toString(), json.toString());
    }

    public void testFromObject_array() {
        try {
            JSONObject.fromObject(new ArrayList());
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // OK
        }

        try {
            JSONObject.fromObject(new String[]{"json"});
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // OK
        }
    }

    public void testFromObject_Bean() {
        JSONObject json = JSONObject.fromObject(new BeanA());
        assertEquals(true, json.getBoolean("bool"));
        assertEquals(42, json.getInt("integer"));
        assertEquals("json", json.getString("string"));
    }

    public void testFromObject_BeanWithFunc() {
        JSONObject json = JSONObject.fromObject(new BeanWithFunc("return a;"));
        assertNotNull(json.get("function"));
        assertTrue(JSONUtils.isFunction(json.get("function")));
        assertEquals("function(){ return a; }", json.get("function")
            .toString());
    }

    public void testFromObject_DynaBean() throws Exception {
        JSONObject json = JSONObject.fromObject(createDynaBean());
        assertEquals("json", json.getString("name"));
    }

    public void testFromObject_emptyBean() {
        EmptyBean bean = new EmptyBean();
        JSONObject json = JSONObject.fromObject(bean);
        JSONObject expected = new JSONObject();
        expected.element("arrayp", new JSONArray());
        expected.element("listp", new JSONArray());
        expected.element("bytep", new Integer(0));
        expected.element("shortp", new Integer(0));
        expected.element("intp", new Integer(0));
        expected.element("longp", new Integer(0));
        expected.element("floatp", new Integer(0));
        expected.element("doublep", new Double(0));
        expected.element("charp", "");
        expected.element("stringp", "");

        Assertions.assertEquals(expected, json);
    }

    public void testFromObject_ExtendedBean() {
        JSONObject json = JSONObject.fromObject(new BeanB());
        assertEquals(true, json.getBoolean("bool"));
        assertEquals(42, json.getInt("integer"));
        assertEquals("json", json.getString("string"));
        assertNotNull(json.get("intarray"));
    }

    public void testFromObject_ignoreTransientFields() {
        jsonConfig.setIgnoreTransientFields(true);
        TransientBean bean = new TransientBean();
        bean.setValue(42);
        bean.setTransientValue(84);
        JSONObject jsonObject = JSONObject.fromObject(bean, jsonConfig);
        assertTrue(jsonObject.has("value"));
        assertFalse(jsonObject.has("transientValue"));
    }

    public void testFromObject_JSONObject() {
        JSONObject expected = new JSONObject().element("id", "1")
            .element("name", "json");
        JSONObject actual = JSONObject.fromObject(expected);
        Assertions.assertEquals(expected, actual);
    }

    public void testFromObject_JSONString() {
        ObjectJSONStringBean bean = new ObjectJSONStringBean();
        bean.setId(1);
        bean.setName("json");
        JSONObject json = JSONObject.fromObject(bean);
        assertEquals("json", json.getString("name"));
        assertTrue(!json.has("id"));
    }

    public void testFromObject_JSONTokener() {
        JSONTokener jsonTokener = new JSONTokener("{\"string\":\"json\"}");
        JSONObject json = JSONObject.fromObject(jsonTokener);
        assertEquals("json", json.getString("string"));
    }

    public void testFromObject_Map() {
        Map map = new HashMap();
        map.put("bool", Boolean.TRUE);
        map.put("integer", new Integer(42));
        map.put("string", "json");
        map.put("array", JSONArray.fromObject("[1]"));
        map.put("object", JSONObject.fromObject("{\"name\":\"json\"}"));

        JSONObject json = JSONObject.fromObject(map);
        assertEquals(true, json.getBoolean("bool"));
        assertEquals(42, json.getInt("integer"));
        assertEquals("json", json.getString("string"));
        Assertions.assertEquals(JSONArray.fromObject("[1]"), json.getJSONArray("array"));
        Assertions.assertEquals(JSONObject.fromObject("{\"name\":\"json\"}"),
            json.getJSONObject("object"));
    }

    public void testFromObject_nested_bean() {
        JSONObject json = JSONObject.fromObject(new BeanC());
        assertNotNull(json.get("beanA"));
        assertNotNull(json.get("beanB"));
    }

    public void testFromObject_null() {
        JSONObject json = JSONObject.fromObject(null);
        assertTrue(json.isNullObject());
        assertEquals(JSONNull.getInstance()
            .toString(), json.toString());
    }

    public void testFromObject_ObjectBean() {
        // FR 1611204
        ObjectBean bean = new ObjectBean();
        bean.setPbyte(Byte.valueOf("1"));
        bean.setPshort(Short.valueOf("1"));
        bean.setPint(Integer.valueOf("1"));
        bean.setPlong(Long.valueOf("1"));
        bean.setPfloat(Float.valueOf("1"));
        bean.setPdouble(Double.valueOf("1"));
        bean.setPchar(new Character('1'));
        bean.setPboolean(Boolean.TRUE);
        bean.setPstring("json");
        bean.setParray(new String[]{"a", "b"});
        bean.setPbean(new BeanA());
        List list = new ArrayList();
        list.add("1");
        list.add("2");
        bean.setPlist(list);
        Map map = new HashMap();
        map.put("string", "json");
        bean.setPmap(map);
        bean.setPfunction(new JSONFunction("this;"));

        JSONObject json = JSONObject.fromObject(bean);
        assertEquals(1, json.getInt("pbyte"));
        assertEquals(1, json.getInt("pshort"));
        assertEquals(1, json.getInt("pint"));
        assertEquals(1, json.getInt("plong"));
        assertEquals(1d, json.getDouble("pfloat"), 0d);
        assertEquals(1d, json.getDouble("pdouble"), 0d);
        assertTrue(json.getBoolean("pboolean"));
        assertEquals("json", json.get("pstring"));
        Assertions.assertEquals(JSONArray.fromObject("['a','b']"), json.getJSONArray("parray"));
        Assertions.assertEquals(JSONArray.fromObject("['1','2']"), json.getJSONArray("plist"));
        assertEquals("1", json.getString("pchar"));
        JSONObject b = new JSONObject().element("string", "json")
            .element("integer", "42")
            .element("bool", "true");
        Assertions.assertEquals(b, json.getJSONObject("pbean"));
        b = new JSONObject().element("string", "json");
        Assertions.assertEquals(b, json.getJSONObject("pmap"));
    }

    public void testFromObject_ObjectBean_empty() {
        // FR 1611204
        ObjectBean bean = new ObjectBean();
        JSONObject json = JSONObject.fromObject(bean);

        String[] keys = {"pbyte", "pshort", "pint", "plong", "pfloat", "pdouble", "pboolean",
            "pchar", "pstring", "parray", "plist", "pmap", "pbean"};
        for (int i = 0; i < keys.length; i++) {
            assertTrue(JSONNull.getInstance()
                .equals(json.get(keys[i])));
        }
    }

    public void testFromObject_String() {
        JSONObject json = JSONObject.fromObject("{\"string\":\"json\"}");
        assertEquals("json", json.getString("string"));
    }

    public void testFromObject_toBean_DynaBean() {
        // bug report 1540137

        String jsondata = "{\"person\":{\"phone\":[\"111-222-3333\",\"777-888-9999\"],"
            + "\"address\":{\"street\":\"123 somewhere place\",\"zip\":\"30005\",\"city\":\"Alpharetta\"},"
            + "\"email\":[\"allen@work.com\",\"allen@home.net\"],\"name\":\"Allen\"}}";

        JSONObject jsonobj = JSONObject.fromObject(jsondata);
        Object bean = JSONObject.toBean(jsonobj);
        // bean is a DynaBean
        assertTrue(bean instanceof MorphDynaBean);
        // convert the DynaBean to a JSONObject again
        JSONObject jsonobj2 = JSONObject.fromObject(bean);

        assertNotNull(jsonobj.getJSONObject("person"));
        assertFalse(JSONUtils.isNull(jsonobj.getJSONObject("person")));
        assertNotNull(jsonobj2.getJSONObject("person"));
        assertFalse(JSONUtils.isNull(jsonobj2.getJSONObject("person")));

        JSONObject person1 = jsonobj.getJSONObject("person");
        JSONObject person2 = jsonobj2.getJSONObject("person");
        assertEquals(person1.get("name"), person2.get("name"));
        assertEquals(person1.get("phone")
            .toString(), person2.get("phone")
            .toString());
        assertEquals(person1.get("email")
            .toString(), person2.get("email")
            .toString());
        JSONObject address1 = person1.getJSONObject("address");
        JSONObject address2 = person2.getJSONObject("address");
        assertEquals(address1.get("street"), address2.get("street"));
        assertEquals(address1.get("zip"), address2.get("zip"));
        assertEquals(address1.get("city"), address2.get("city"));
    }

    public void testFromObject_use_wrappers() {
        JSONObject json = JSONObject.fromObject(Boolean.TRUE);
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Byte(Byte.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Short(Short.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Integer(Integer.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Long(Long.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Float(Float.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Double(Double.MIN_VALUE));
        assertTrue(json.isEmpty());
        json = JSONObject.fromObject(new Character('A'));
        assertTrue(json.isEmpty());
    }

    public void testFromObject_withCustomDefaultValueProcessor() {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerDefaultValueProcessor(Integer.class, new NumberDefaultValueProcessor());
        JSONObject jsonObject = JSONObject.fromObject(new NumberBean(), jsonConfig);
        assertNotNull(jsonObject);
        assertEquals(new Integer(0), jsonObject.get("pwbyte"));
        assertEquals(new Integer(0), jsonObject.get("pwshort"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwint"));
        assertEquals(new Integer(0), jsonObject.get("pwlong"));
        assertEquals(new Integer(0), jsonObject.get("pwfloat"));
        assertEquals(new Double(0), jsonObject.get("pwdouble"));
        assertEquals(new Integer(0), jsonObject.get("pbigdec"));
        assertEquals(new Integer(0), jsonObject.get("pbigint"));
        assertEquals(new Integer(0), jsonObject.get("pbyte"));
        assertEquals(new Integer(0), jsonObject.get("pshort"));
        assertEquals(new Integer(0), jsonObject.get("pint"));
        assertEquals(new Integer(0), jsonObject.get("plong"));
        assertEquals(new Double(0), jsonObject.get("pfloat"));
        assertEquals(new Double(0), jsonObject.get("pdouble"));
    }

    public void testFromObject_withCustomDefaultValueProcessor_andMatcher() {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerDefaultValueProcessor(Integer.class, new NumberDefaultValueProcessor());
        jsonConfig.setDefaultValueProcessorMatcher(new NumberDefaultValueProcessorMatcher());
        JSONObject jsonObject = JSONObject.fromObject(new NumberBean(), jsonConfig);
        assertNotNull(jsonObject);
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pbigdec"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pbigint"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwbyte"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwshort"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwint"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwlong"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwfloat"));
        assertEquals(NumberDefaultValueProcessor.NUMBER, jsonObject.get("pwdouble"));
        assertEquals(new Integer(0), jsonObject.get("pbyte"));
        assertEquals(new Integer(0), jsonObject.get("pshort"));
        assertEquals(new Integer(0), jsonObject.get("pint"));
        assertEquals(new Integer(0), jsonObject.get("plong"));
        assertEquals(new Double(0), jsonObject.get("pfloat"));
        assertEquals(new Double(0), jsonObject.get("pdouble"));
    }

    public void testFromObject_withExcludesPerClass() {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerPropertyExclusion(BeanA.class, "bool");
        JSONObject jsonA = JSONObject.fromObject(new BeanA(), jsonConfig);
        JSONObject jsonB = JSONObject.fromObject(new BeanB(), jsonConfig);
        assertNotNull(jsonA);
        assertNotNull(jsonB);
        assertFalse(jsonA.has("bool"));
        assertTrue(jsonB.has("bool"));
    }

    public void testFromObject_withExcludesPerClassAndMatcher() {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerPropertyExclusion(BeanA.class, "bool");
        jsonConfig.setPropertyExclusionClassMatcher(new BeanAPropertyExclusionClassMatcher());
        JSONObject jsonA = JSONObject.fromObject(new BeanA(), jsonConfig);
        JSONObject jsonB = JSONObject.fromObject(new BeanB(), jsonConfig);
        assertNotNull(jsonA);
        assertNotNull(jsonB);
        assertFalse(jsonA.has("bool"));
        assertFalse(jsonB.has("bool"));
    }

    public void testFromObject_withFilters() {
        PrimitiveBean bean = new PrimitiveBean();
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setJsonPropertyFilter(new NumberPropertyFilter());
        JSONObject json = JSONObject.fromObject(bean, jsonConfig);
        assertNotNull(json);
        assertTrue(json.has("pbean"));
        assertTrue(json.has("pclass"));
        assertTrue(json.has("pexcluded"));
        assertTrue(json.has("pfunction"));
        assertTrue(json.has("plist"));
        assertTrue(json.has("pmap"));
        assertTrue(json.has("pstring"));
        assertTrue(json.has("parray"));

        assertTrue(json.has("pboolean"));
        assertTrue(!json.has("pbyte"));
        assertTrue(!json.has("pshort"));
        assertTrue(!json.has("pint"));
        assertTrue(!json.has("plong"));
        assertTrue(!json.has("pfloat"));
        assertTrue(!json.has("pdouble"));
        assertTrue(json.has("pchar"));
    }

    public void testFromObject_withFiltersAndExcludes() {
        PrimitiveBean bean = new PrimitiveBean();
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setJsonPropertyFilter(new NumberPropertyFilter());
        jsonConfig.setExcludes(new String[]{"pexcluded"});
        JSONObject json = JSONObject.fromObject(bean, jsonConfig);
        assertNotNull(json);
        assertTrue(json.has("pbean"));
        assertTrue(json.has("pclass"));
        assertTrue(!json.has("pexcluded"));
        assertTrue(json.has("pfunction"));
        assertTrue(json.has("plist"));
        assertTrue(json.has("pmap"));
        assertTrue(json.has("pstring"));
        assertTrue(json.has("parray"));

        assertTrue(json.has("pboolean"));
        assertTrue(!json.has("pbyte"));
        assertTrue(!json.has("pshort"));
        assertTrue(!json.has("pint"));
        assertTrue(!json.has("plong"));
        assertTrue(!json.has("pfloat"));
        assertTrue(!json.has("pdouble"));
        assertTrue(json.has("pchar"));
    }

    public void testFromString_null_String() {
        JSONObject json = JSONObject.fromObject(null);
        assertTrue(json.isNullObject());
        assertEquals(JSONNull.getInstance()
            .toString(), json.toString());
    }

    public void testHas() {
        assertFalse(new JSONObject().has("any"));
        assertTrue(new JSONObject().element("any", "value")
            .has("any"));
    }

    public void testLength() {
        assertEquals(0, new JSONObject().size());
    }

    public void testLength_nullObject() {
      /*
      try{
         new JSONObject( true ).size();
         fail( "Expected a JSONException" );
      }catch( JSONException expected ){
         // ok
      }
      */
        assertEquals(0, new JSONObject(true).size());
    }

    public void testOptBoolean() {
        assertFalse(new JSONObject().optBoolean("any"));
    }

    public void testOptBoolean_defaultValue() {
        assertTrue(new JSONObject().optBoolean("any", true));
    }

    public void testOptDouble() {
        assertTrue(Double.isNaN(new JSONObject().optDouble("any")));
    }

    public void testOptDouble_defaultValue() {
        assertEquals(2d, new JSONObject().optDouble("any", 2d), 0d);
    }

    public void testOptInt() {
        assertEquals(0, new JSONObject().optInt("any"));
    }

    public void testOptInt_defaultValue() {
        assertEquals(1, new JSONObject().optInt("any", 1));
    }

    public void testOptJSONArray() {
        JSONObject json = new JSONObject();
        assertNull(json.optJSONArray("a"));
        json.element("a", new JSONArray());
        Assertions.assertEquals(new JSONArray(), json.optJSONArray("a"));
    }

    public void testOptJSONObject() {
        JSONObject json = new JSONObject();
        assertNull(json.optJSONObject("a"));
        json.element("a", new JSONObject());
        Assertions.assertEquals(new JSONObject(), json.optJSONObject("a"));
    }

    public void testOptLong() {
        assertEquals(0L, new JSONObject().optLong("any"));
    }

    public void testOptLong_defaultValue() {
        assertEquals(1L, new JSONObject().optLong("any", 1L));
    }

    public void testOptString() {
        assertEquals("", new JSONObject().optString("any"));
    }

    public void testOptString_defaultValue() {
        assertEquals("json", new JSONObject().optString("any", "json"));
    }

    public void testToBean() throws Exception {
        String json = "{name=\"json\",bool:true,int:1,double:2.2,func:function(a){ return a; },array:[1,2]}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        Object bean = JSONObject.toBean(jsonObject);
        assertEquals(jsonObject.get("name"), PropertyUtils.getProperty(bean, "name"));
        assertEquals(jsonObject.get("bool"), PropertyUtils.getProperty(bean, "bool"));
        assertEquals(jsonObject.get("int"), PropertyUtils.getProperty(bean, "int"));
        assertEquals(jsonObject.get("double"), PropertyUtils.getProperty(bean, "double"));
        assertEquals(jsonObject.get("func"), PropertyUtils.getProperty(bean, "func"));
        List expected = (List) JSONArray.toCollection(jsonObject.getJSONArray("array"));
        Assertions.assertEquals(expected, (List) PropertyUtils.getProperty(bean, "array"));
    }

    public void testToBean_BeanA() {
        String json = "{bool:true,integer:1,string:\"json\"}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        BeanA bean = (BeanA) JSONObject.toBean(jsonObject, BeanA.class);
        assertEquals(jsonObject.get("bool"), Boolean.valueOf(bean.isBool()));
        assertEquals(jsonObject.get("integer"), new Integer(bean.getInteger()));
        assertEquals(jsonObject.get("string"), bean.getString());
    }

    public void testToBean_BeanB() {
        String json = "{bool:true,integer:1,string:\"json\",intarray:[4,5,6]}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        BeanB bean = (BeanB) JSONObject.toBean(jsonObject, BeanB.class);
        assertEquals(jsonObject.get("bool"), Boolean.valueOf(bean.isBool()));
        assertEquals(jsonObject.get("integer"), new Integer(bean.getInteger()));
        assertEquals(jsonObject.get("string"), bean.getString());
        Assertions.assertEquals(bean.getIntarray(),
            JSONArray.toArray(jsonObject.getJSONArray("intarray")));
    }

    // This already works without preserving floating point
    public void testToDynaBean_convertDouble() {
        String json = "{double:1.0}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        Object bean = JSONObject.toBean(jsonObject);
        assertEquals(1.0d, ((DynaBean) bean).get("double"));
        assertEquals(Double.class, ((DynaBean) bean).get("double").getClass());
    }

    public void testToBean_ClassBean() {
        JSONObject json = new JSONObject();
        json.element("klass", "java.lang.Object");

        ClassBean bean = (ClassBean) JSONObject.toBean(json, ClassBean.class);
        assertEquals(Object.class, bean.getKlass());
    }

    public void testToBean_DynaBean__BigInteger_BigDecimal() {
        BigInteger l = new BigDecimal("1.7976931348623157E308").toBigInteger();
        BigDecimal m = new BigDecimal("1.7976931348623157E307").add(new BigDecimal("0.0001"));
        JSONObject json = new JSONObject().element("i", BigInteger.ZERO)
            .element("d", MorphUtils.BIGDECIMAL_ONE)
            .element("bi", l)
            .element("bd", m);
        Object bean = JSONObject.toBean(json);
        Object i = ((MorphDynaBean) bean).get("i");
        Object d = ((MorphDynaBean) bean).get("d");
        assertTrue(i instanceof Integer);
        assertTrue(d instanceof Integer);

        Object bi = ((MorphDynaBean) bean).get("bi");
        Object bd = ((MorphDynaBean) bean).get("bd");
        assertTrue(bi instanceof BigInteger);
        assertTrue(bd instanceof BigDecimal);
    }

    public void testToBean_emptyBean() {
        EmptyBean bean = new EmptyBean();

        JSONObject json = JSONObject.fromObject(bean);
        JSONObject expected = new JSONObject();
        expected.element("arrayp", new JSONArray());
        expected.element("listp", new JSONArray());
        expected.element("bytep", new Integer(0));
        expected.element("shortp", new Integer(0));
        expected.element("intp", new Integer(0));
        expected.element("longp", new Integer(0));
        expected.element("floatp", new Integer(0));
        expected.element("doublep", new Double(0));
        expected.element("charp", "");
        expected.element("stringp", "");

        Assertions.assertEquals(expected, json);

        EmptyBean bean2 = (EmptyBean) JSONObject.toBean(json, EmptyBean.class);

        ArrayAssertions.assertEquals(new Object[0], bean2.getArrayp());
        Assertions.assertEquals(new ArrayList(), bean2.getListp());
        Assertions.assertEquals(new Byte((byte) 0), bean2.getBytep());
        Assertions.assertEquals(new Short((short) 0), bean2.getShortp());
        Assertions.assertEquals(new Integer(0), bean2.getIntp());
        Assertions.assertEquals(new Long(0), bean2.getLongp());
        Assertions.assertEquals(new Float(0), bean2.getFloatp());
        Assertions.assertEquals(new Double(0), bean2.getDoublep());
        Assertions.assertEquals(new Character('\0'), bean2.getCharp());
        Assertions.assertEquals("", bean2.getStringp());
    }

    public void testToBean_interface() {
        // BUG 1542104

        try {
            JSONObject.toBean(JSONObject.fromObject("{\"int\":1}"), Serializable.class);
            fail("Expected a JSONException");
        } catch (JSONException expected) {
            // ok
        }
    }

    public void testToBean_Map() {
        // BUG 1542104

        Map map = new HashMap();
        map.put("name", "json");
        Object obj = JSONObject.toBean(JSONObject.fromObject(map), Map.class);
        assertTrue(obj instanceof Map);
        assertEquals(map.get("name"), ((Map) obj).get("name"));
    }

    public void testToBean_nested() throws Exception {
        String json = "{name=\"json\",bool:true,int:1,double:2.2,func:function(a){ return a; },nested:{nested:true}}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        Object bean = JSONObject.toBean(jsonObject);
        assertEquals(jsonObject.get("name"), PropertyUtils.getProperty(bean, "name"));
        assertEquals(jsonObject.get("bool"), PropertyUtils.getProperty(bean, "bool"));
        assertEquals(jsonObject.get("int"), PropertyUtils.getProperty(bean, "int"));
        assertEquals(jsonObject.get("double"), PropertyUtils.getProperty(bean, "double"));
        assertEquals(jsonObject.get("func"), PropertyUtils.getProperty(bean, "func"));
        JSONObject nestedJson = jsonObject.getJSONObject("nested");
        Object nestedBean = PropertyUtils.getProperty(bean, "nested");
        assertEquals(nestedJson.get("nested"), PropertyUtils.getProperty(nestedBean, "nested"));
    }

    public void testToBean_nested_beans__null_object() throws Exception {
        // BUG 1553617

        String json = "{\"beanA\":{bool:true,integer:1,string:\"jsonbean\"},\"beanB\":null}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        BeanC bean = (BeanC) JSONObject.toBean(jsonObject, BeanC.class);
        assertNotNull(bean);
        BeanA beanA = bean.getBeanA();
        assertNotNull(beanA);
        assertEquals(true, beanA.isBool());
        assertEquals(1, beanA.getInteger());
        assertEquals("jsonbean", beanA.getString());
        BeanB beanB = bean.getBeanB();
        assertNull(beanB);
    }

    public void testToBean_nested_beans_in_list__beans() {
        // BUG 1592799

        ListingBean listingBean = new ListingBean();

        ValueBean beanA1 = new ValueBean();
        beanA1.setValue(90000);
        ValueBean beanA2 = new ValueBean();
        beanA2.setValue(91000);

        listingBean.addAttribute(beanA1);
        listingBean.addAttribute(beanA2);

        JSONObject jsonObject = JSONObject.fromObject(listingBean);
        Map classMap = new HashMap();
        classMap.put("attributes", ValueBean.class);
        ListingBean listingBean2 = (ListingBean) JSONObject.toBean(jsonObject, ListingBean.class,
            classMap);
        List attributes = listingBean2.getAttributes();
        Object ba = attributes.get(0);
        Object bb = attributes.get(1);

        assertTrue(ba instanceof ValueBean);
        assertTrue(bb instanceof ValueBean);
        assertEquals(beanA1.getValue(), ((ValueBean) ba).getValue());
        assertEquals(beanA2.getValue(), ((ValueBean) bb).getValue());
    }

    public void testToBean_nested_beans_in_list__DynaBean() {
        // BUG 1592799

        ListingBean listingBean = new ListingBean();

        ValueBean beanA1 = new ValueBean();
        beanA1.setValue(90000);
        ValueBean beanA2 = new ValueBean();
        beanA2.setValue(91000);

        listingBean.addAttribute(beanA1);
        listingBean.addAttribute(beanA2);

        JSONObject jsonObject = JSONObject.fromObject(listingBean);
        ListingBean listingBean2 = (ListingBean) JSONObject.toBean(jsonObject, ListingBean.class);
        List attributes = listingBean2.getAttributes();
        Object ba = attributes.get(0);
        Object bb = attributes.get(1);

        assertTrue(ba instanceof MorphDynaBean);
        assertTrue(bb instanceof MorphDynaBean);
        assertEquals(new Integer(beanA1.getValue()), ((MorphDynaBean) ba).get("value"));
        assertEquals(new Integer(beanA2.getValue()), ((MorphDynaBean) bb).get("value"));
    }

    public void testToBean_nested_beans_in_map__beans() {
        // BUG 1542092

        MappingBean mappingBean = new MappingBean();

        ValueBean beanA = new ValueBean();
        beanA.setValue(90000);
        ValueBean beanB = new ValueBean();
        beanB.setValue(91000);

        mappingBean.addAttribute("beanA", beanA);
        mappingBean.addAttribute("beanB", beanB);

        JSONObject jsonObject = JSONObject.fromObject(mappingBean);
        Map classMap = new HashMap();
        classMap.put("bean.*", ValueBean.class);
        MappingBean mappingBean2 = (MappingBean) JSONObject.toBean(jsonObject, MappingBean.class,
            classMap);
        Object ba = mappingBean2.getAttributes()
            .get("beanA");
        Object bb = mappingBean2.getAttributes()
            .get("beanB");
        assertTrue(ba instanceof ValueBean);
        assertTrue(bb instanceof ValueBean);
        assertEquals(beanA.getValue(), ((ValueBean) ba).getValue());
        assertEquals(beanB.getValue(), ((ValueBean) bb).getValue());
    }

    public void testToBean_nested_beans_in_map__DynaBean() {
        // BUG 1542092

        MappingBean mappingBean = new MappingBean();

        ValueBean beanA = new ValueBean();
        beanA.setValue(90000);
        ValueBean beanB = new ValueBean();
        beanB.setValue(91000);

        mappingBean.addAttribute("beanA", beanA);
        mappingBean.addAttribute("beanB", beanB);

        JSONObject jsonObject = JSONObject.fromObject(mappingBean);
        MappingBean mappingBean2 = (MappingBean) JSONObject.toBean(jsonObject, MappingBean.class);
        Object ba = mappingBean2.getAttributes()
            .get("beanA");
        Object bb = mappingBean2.getAttributes()
            .get("beanB");
        assertTrue(ba instanceof MorphDynaBean);
        assertTrue(bb instanceof MorphDynaBean);
        assertEquals(new Integer(beanA.getValue()), ((MorphDynaBean) ba).get("value"));
        assertEquals(new Integer(beanB.getValue()), ((MorphDynaBean) bb).get("value"));
    }

    public void testToBean_nested_beans_in_set__beans() {
        // FR 1847116

        SetBean setBean = new SetBean();

        ValueBean beanA1 = new ValueBean();
        beanA1.setValue(90000);
        ValueBean beanA2 = new ValueBean();
        beanA2.setValue(91000);

        setBean.addAttribute(beanA1);
        setBean.addAttribute(beanA2);

        JSONObject jsonObject = JSONObject.fromObject(setBean);
        Map classMap = new HashMap();
        classMap.put("attributes", ValueBean.class);
        SetBean setBean2 = (SetBean) JSONObject.toBean(jsonObject, SetBean.class, classMap);
        assertEquals(setBean, setBean2);
    }

    public void testToBean_nested_beans_in_set__DynaBean() {
        // FR 1847116

        SetBean setBean = new SetBean();

        ValueBean beanA1 = new ValueBean();
        beanA1.setValue(90000);
        ValueBean beanA2 = new ValueBean();
        beanA2.setValue(91000);

        setBean.addAttribute(beanA1);
        setBean.addAttribute(beanA2);

        JSONObject jsonObject = JSONObject.fromObject(setBean);
        //SetBean setBean2 = (SetBean) JSONObject.toBean( jsonObject, SetBean.class );
        //assertEquals( setBean, setBean2 );
    }

    public void testToBean_nested_dynabeans__null_object() throws Exception {
        // BUG 1553617

        String json = "{\"beanA\":{bool:true,integer:1,string:\"jsonbean\"},\"beanB\":null}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        Object bean = JSONObject.toBean(jsonObject);
        assertNotNull(bean);
        Object beanA = PropertyUtils.getProperty(bean, "beanA");
        assertNotNull(beanA);
        assertEquals(Boolean.TRUE, PropertyUtils.getProperty(beanA, "bool"));
        assertEquals(new Integer(1), PropertyUtils.getProperty(beanA, "integer"));
        assertEquals("jsonbean", PropertyUtils.getProperty(beanA, "string"));
        Object beanB = PropertyUtils.getProperty(bean, "beanB");
        assertNull(beanB);
    }

    public void testtoBean_noWriteMethod() {
        JSONObject json = new JSONObject();
        json.element("propertyWithNoReadMethod", "json");
        json.element("propertyWithNoWriteMethod", "value");
        PropertyBean bean = (PropertyBean) JSONObject.toBean(json, PropertyBean.class);
        assertNotNull(bean);
        assertEquals("json", bean.valueOfPropertyWithNoReadMethod());
        assertEquals("json", bean.getPropertyWithNoWriteMethod());
    }

    public void testToBean_null() {
        assertNull(JSONObject.toBean((JSONObject) null));
    }

    public void testToBean_null_2() {
        assertNull(JSONObject.toBean(null, BeanA.class));
    }

    public void testToBean_null_object() {
        JSONObject jsonObject = new JSONObject(true);
        BeanA bean = (BeanA) JSONObject.toBean(jsonObject, BeanA.class);
        assertNull(bean);
    }

    public void testToBean_null_values() {
        // bug report 1540196

        String json = "{\"items\":[[\"000\"],[\"010\", \"011\"],[\"020\"]]}";
        JSONObject jsonObject = JSONObject.fromObject(json);

        BeanFoo foo = (BeanFoo) JSONObject.toBean(jsonObject, BeanFoo.class);
        assertNotNull(foo);
        assertNotNull(foo.getItems());
        String[][] items = foo.getItems();
        assertEquals(3, items.length);
        assertEquals("000", items[0][0]);
        assertEquals("010", items[1][0]);
        assertEquals("011", items[1][1]);
        assertEquals("020", items[2][0]);
    }

    public void testToBean_NumberBean() {
        JSONObject json = new JSONObject();
        json.element("pbyte", new Byte((byte) 2));
        json.element("pshort", new Short((short) 2));
        json.element("pint", new Integer(2));
        json.element("plong", new Long(2));
        json.element("pfloat", new Float(2));
        json.element("pdouble", new Double(2));
        json.element("pbigint", new BigInteger("2"));
        json.element("pbigdec", new BigDecimal("2"));

        NumberBean bean = (NumberBean) JSONObject.toBean(json, NumberBean.class);
        assertEquals((byte) 2, bean.getPbyte());
        assertEquals((short) 2, bean.getPshort());
        assertEquals(2, bean.getPint());
        assertEquals(2L, bean.getPlong());
        assertEquals(2f, bean.getPfloat(), 0f);
        assertEquals(2d, bean.getPdouble(), 0d);
        assertEquals(new BigInteger("2"), bean.getPbigint());
        assertEquals(new BigDecimal("2"), bean.getPbigdec());
    }

    public void testToBean_NumberBean_2() {
        JSONObject json = new JSONObject();
        json.element("pbyte", new Integer(2));
        json.element("pshort", new Integer(2));
        json.element("pint", new Integer(2));
        json.element("plong", new Integer(2));
        json.element("pfloat", new Integer(2));
        json.element("pdouble", new Integer(2));
        json.element("pbigint", new Integer(2));
        json.element("pbigdec", new Integer(2));

        NumberBean bean = (NumberBean) JSONObject.toBean(json, NumberBean.class);
        assertEquals((byte) 2, bean.getPbyte());
        assertEquals((short) 2, bean.getPshort());
        assertEquals(2, bean.getPint());
        assertEquals(2L, bean.getPlong());
        assertEquals(2f, bean.getPfloat(), 0f);
        assertEquals(2d, bean.getPdouble(), 0d);
        assertEquals(new BigInteger("2"), bean.getPbigint());
        assertEquals(new BigDecimal("2"), bean.getPbigdec());
    }

    public void testToBean_ObjectBean() {
        // FR 1611204
        ObjectBean bean = new ObjectBean();
        bean.setPbyte(Byte.valueOf("1"));
        bean.setPshort(Short.valueOf("1"));
        bean.setPint(Integer.valueOf("1"));
        bean.setPlong(Long.valueOf("1"));
        bean.setPfloat(Float.valueOf("1"));
        bean.setPdouble(Double.valueOf("1"));
        bean.setPchar(new Character('1'));
        bean.setPboolean(Boolean.TRUE);
        bean.setPstring("json");
        bean.setParray(new String[]{"a", "b"});
        bean.setPbean(new BeanA());
        List list = new ArrayList();
        list.add("1");
        list.add("2");
        bean.setPlist(list);
        Map map = new HashMap();
        map.put("string", "json");
        bean.setPmap(map);
        bean.setPfunction(new JSONFunction("this;"));
        JSONObject json = JSONObject.fromObject(bean);
        Map classMap = new HashMap();
        classMap.put("pbean", BeanA.class);
        ObjectBean obj = (ObjectBean) JSONObject.toBean(json, ObjectBean.class, classMap);
        assertEquals(Integer.valueOf("1"), obj.getPbyte());
        assertEquals(Integer.valueOf("1"), obj.getPshort());
        assertEquals(Integer.valueOf("1"), obj.getPint());
        assertEquals(Integer.valueOf("1"), obj.getPlong());
        assertEquals(Double.valueOf("1"), obj.getPfloat());
        assertEquals(Double.valueOf("1"), obj.getPdouble());
        assertEquals("1", obj.getPchar());
        assertEquals("json", obj.getPstring());
        List l = new ArrayList();
        l.add("a");
        l.add("b");
        ArrayAssertions.assertEquals(l.toArray(), (Object[]) obj.getParray());
        l = new ArrayList();
        l.add("1");
        l.add("2");
        ArrayAssertions.assertEquals(l.toArray(), (Object[]) obj.getPlist());
        assertEquals(new BeanA(), obj.getPbean());
        assertTrue(obj.getPmap() instanceof MorphDynaBean);
    }

    public void testToBean_ObjectBean_empty() throws Exception {
        // FR 1611204
        ObjectBean bean = new ObjectBean();
        JSONObject json = JSONObject.fromObject(bean);
        Map classMap = new HashMap();
        classMap.put("bean", BeanA.class);
        ObjectBean obj = (ObjectBean) JSONObject.toBean(json, ObjectBean.class, classMap);

        String[] keys = {"pbyte", "pshort", "pint", "plong", "pfloat", "pdouble", "pboolean",
            "pchar", "pstring", "parray", "plist", "pmap", "pbean"};
        for (int i = 0; i < keys.length; i++) {
            assertNull(PropertyUtils.getProperty(obj, keys[i]));
        }
    }

    public void testToBean_rootObject() {
        JSONObject json = new JSONObject().element("bool", "false")
            .element("integer", 84)
            .element("string", "bean");
        BeanA expected = new BeanA();
        BeanA actual = (BeanA) JSONObject.toBean(json, expected, new JsonConfig());
        assertNotNull(actual);
        assertEquals(expected, actual);
        assertFalse(actual.isBool());
        assertEquals(84, actual.getInteger());
        assertEquals("bean", actual.getString());
    }

    public void testToBean_withFilters() {
        BeanA bean = new BeanA();
        bean.setBool(false);
        bean.setInteger(84);
        bean.setString("filter");
        JSONObject json = JSONObject.fromObject(bean);
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setRootClass(BeanA.class);
        jsonConfig.setJavaPropertyFilter(new BeanAPropertyFilter());
        BeanA actual = (BeanA) JSONObject.toBean(json, jsonConfig);
        assertNotNull(actual);
        assertTrue(actual.isBool());
        assertEquals(42, actual.getInteger());
        assertEquals("filter", actual.getString());
    }

    public void testToBean_withNonJavaIdentifier_camelCase_Strategy() {
        JSONObject json = new JSONObject().element("camel case", "json");
        jsonConfig.setJavaIdentifierTransformer(JavaIdentifierTransformer.CAMEL_CASE);
        jsonConfig.setRootClass(JavaIdentifierBean.class);
        JavaIdentifierBean bean = (JavaIdentifierBean) JSONObject.toBean(json, jsonConfig);
        assertNotNull(bean);
        assertEquals("json", bean.getCamelCase());
    }

    public void testToBean_withNonJavaIdentifier_underScore_Strategy() {
        JSONObject json = new JSONObject().element("under score", "json");
        jsonConfig.setJavaIdentifierTransformer(JavaIdentifierTransformer.UNDERSCORE);
        jsonConfig.setRootClass(JavaIdentifierBean.class);
        JavaIdentifierBean bean = (JavaIdentifierBean) JSONObject.toBean(json, jsonConfig);
        assertNotNull(bean);
        assertEquals("json", bean.getUnder_score());
    }

    public void testToBean_withNonJavaIdentifier_whitespace_Strategy() {
        JSONObject json = new JSONObject().element(" white space ", "json");
        jsonConfig.setJavaIdentifierTransformer(JavaIdentifierTransformer.WHITESPACE);
        jsonConfig.setRootClass(JavaIdentifierBean.class);
        JavaIdentifierBean bean = (JavaIdentifierBean) JSONObject.toBean(json, jsonConfig);
        assertNotNull(bean);
        assertEquals("json", bean.getWhitespace());
    }

    public void testToBean_withPropertySetStrategy() {
        JSONObject json = new JSONObject().element("key", "value");
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setRootClass(MappingBean.class);
        jsonConfig.setPropertySetStrategy(new MappingPropertySetStrategy());
        MappingBean bean = (MappingBean) JSONObject.toBean(json, jsonConfig);
        assertNotNull(bean);
        assertEquals("value", bean.getAttributes()
            .get("key"));
    }

    public void testToBeanWithJavaPropertyNameProcessor() {
        String json = "{bool:false}";
        JSONObject jsonObject = JSONObject.fromObject(json);
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerJavaPropertyNameProcessor(BeanA.class, new SwapPropertyNameProcessor());
        jsonConfig.setRootClass(BeanA.class);
        BeanA bean = (BeanA) JSONObject.toBean(jsonObject, jsonConfig);
        assertNotNull(bean);
        assertTrue(bean.isBool());
        assertEquals("false", bean.getString());
    }

    public void testToJSONArray() {
        String json = "{bool:true,integer:1,string:\"json\"}";
        JSONArray names = JSONArray.fromObject("['string','integer','bool']");
        JSONObject jsonObject = JSONObject.fromObject(json);
        JSONArray jsonArray = jsonObject.toJSONArray(names);
        assertEquals("json", jsonArray.getString(0));
        assertEquals(1, jsonArray.getInt(1));
        assertTrue(jsonArray.getBoolean(2));
    }

    public void testNumbers() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("double", 1.0d);
        jsonObject.put("float", 1.0f);
        jsonObject.put("integer", 1);

        assertEquals(1.0d, (Double) jsonObject.get("double"), 0d);
        assertEquals(1.0f, (Double) jsonObject.get("float"), 0d);
        assertEquals(Integer.valueOf(1), jsonObject.get("integer"));

        // perform a full roundtrip
        String expected = "{\"double\":1.0,\"float\":1.0,\"integer\":1}";
        JSONAssert.assertEquals(expected, jsonObject);

        JSONObject json = JSONObject.fromObject(expected);
        JSONAssert.assertEquals(jsonObject, json);
        assertEquals(1.0d, (Double) json.get("double"), 0d);
        assertEquals(1.0f, (Double) json.get("float"), 0d);
        assertEquals(Integer.valueOf(1), json.get("integer"));
    }

    public void testBug5StringEscaping() {
        JSONObject json = new JSONObject();
        json.put("key1", "[...]");
        json.put("key2", "[title]");

        assertEquals("{\"key1\":\"[...]\",\"key2\":\"[title]\"}", json.toString());
    }

    protected void setUp() throws Exception {
        jsonConfig = new JsonConfig();
    }

    private MorphDynaBean createDynaBean() throws Exception {
        Map properties = new HashMap();
        properties.put("name", String.class);
        properties.put("func", JSONFunction.class);
        properties.put("jsonstr", JSONString.class);
        properties.put("json", JSON.class);
        properties.put("str", String.class);
        MorphDynaClass dynaClass = new MorphDynaClass(properties);
        MorphDynaBean dynaBean = (MorphDynaBean) dynaClass.newInstance();
        dynaBean.setDynaBeanClass(dynaClass);
        dynaBean.set("name", "json");
        dynaBean.set("func", new JSONFunction("return this;"));
        dynaBean.set("jsonstr", new ObjectJSONStringBean());
        dynaBean.set("json", new JSONObject().element("id", "1"));
        dynaBean.set("str", "[1,2]");
        // JSON Strings can not be null, only empty
        return dynaBean;
    }

    public void test_fromJSONObject() {
    }

    public void testCanonicalWrite() throws Exception {
        JSONArray a = new JSONArray();
        a.add(Boolean.valueOf(true));
        //        a.add(null);
        a.add(Integer.valueOf(1));
        a.add(Double.valueOf(5.3));
        JSONObject o = new JSONObject();
        o.put("key1", "1");
        o.put("key2", "2");
        o.put("key3", "3");
        o.put("string", "123\r\n\b\t\f\\\\u65E5\\u672C\\u8A9E");
        a.add(o);

        StringWriter sw = new StringWriter();
        a.writeCanonical(sw);
        assertEquals(sw.toString(), "[true,1,5.3,{\"key1\":\"1\",\"key2\":\"2\",\"key3\":\"3\",\"string\":\"123\\u000d\\u000a\\u0008\\u0009\\u000c\\\\\\\\u65E5\\\\u672C\\\\u8A9E\"}]");
    }

    public static class BeanAPropertyExclusionClassMatcher extends PropertyExclusionClassMatcher {
        public Object getMatch(Class target, Set set) {
            for (Iterator i = set.iterator(); i.hasNext(); ) {
                Class c = (Class) i.next();
                if (BeanA.class.isAssignableFrom(c)) {
                    return c;
                }
            }
            return null;
        }
    }

    public static class BeanAPropertyFilter implements PropertyFilter {
        public boolean apply(Object source, String name, Object value) {
            if ("bool".equals(name) || "integer".equals(name)) {
                return true;
            }
            return false;
        }

    }

    public static class MappingPropertySetStrategy extends PropertySetStrategy {
        public void setProperty(Object bean, String key, Object value) throws JSONException {
            ((MappingBean) bean).addAttribute(key, value);
        }
    }

    public static class NumberDefaultValueProcessor implements DefaultValueProcessor {
        public static final Integer NUMBER = new Integer(42);

        public Object getDefaultValue(Class type) {
            return NUMBER;
        }
    }

    public static class NumberDefaultValueProcessorMatcher extends DefaultValueProcessorMatcher {
        public Object getMatch(Class target, Set set) {
            for (Iterator i = set.iterator(); i.hasNext(); ) {
                Class c = (Class) i.next();
                if (Number.class.isAssignableFrom(c)) {
                    return c;
                }
            }
            return null;
        }
    }

    public static class NumberPropertyFilter implements PropertyFilter {
        public boolean apply(Object source, String name, Object value) {
            if (value != null && Number.class.isAssignableFrom(value.getClass())) {
                return true;
            }
            return false;
        }
    }

    public static class SwapPropertyNameProcessor implements PropertyNameProcessor {
        public String processPropertyName(Class beanClass, String name) {
            if (name.equals("bool")) {
                return "string";
            }
            return name;
        }
    }
}