Skip to content

yuehcw/JSON.org-Enhancement

Repository files navigation

MileStone1

Task description

  • Fork the JSON org project in GithubLinks https://github.com/stleary/JSON-java to an external site. and make it "your own."

  • Build the library

The org.json package can be built from the command line, Maven, and Gradle. The unit tests can be executed from Maven, Gradle, or individually in an IDE e.g. Eclipse.

Building from the command line

Build the class files from the package root directory /src/main/java

javac org/json/*.java

Create the jar file in the current directory

jar cf json-java.jar org/json/*.class
  • Add an overloaded static method to the XML class with the signature
public static JSONObject toJSONObject(Reader reader, JSONPointer path) throws JSONException {
        JSONObject jo = new JSONObject();
        XMLTokener x = new XMLTokener(reader);
        String[] pathList = path.toString().split("/");
        String stopKey;
        int reachIndex = -1;
        boolean foundTarget = false; // Add a flag to track if the target object is found

        // Check if the last part of the path is a digit
        if (pathList[pathList.length - 1].matches("^\\d+$")) {
            reachIndex = Integer.parseInt(pathList[pathList.length - 1]);
            stopKey = pathList[pathList.length - 2];
        } else {
            stopKey = pathList[pathList.length - 1];
        }

        while (x.more() && !foundTarget) { // Continue parsing until the target is found
            x.skipPast("<");
            if (x.more()) {
                foundTarget = parse2(x, jo, null, XMLParserConfiguration.ORIGINAL, stopKey, reachIndex);
            }
        }

        // Use JSONPointer to extract the sub-object
        if (reachIndex >= 0) {
            return (JSONObject) jo.query(path);
        } else {
            return new JSONObject(jo.query(path).toString());
        }
    }

It reads an XML file into a JSON object and efficiently extracts smaller sub-objects using a specific path. This process, conducted internally, enhances efficiency by halting parsing the moment the desired object is located, eliminating the need to read the entire XML file.

I implement 'parse2' fucntion as helper method to build the toJSONObject function

private static boolean parse2(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, String stopKey, int reachIndex) {
            ......
        } else if (token == SLASH) {
            // Close tag </
            token = x.nextToken();
            if (name == null) {
                throw x.syntaxError("Mismatched close tag " + token);
            }
            if (!token.equals(name)) {
                throw x.syntaxError("Mismatched " + name + " and " + token);
            }
            if (x.nextToken() != GT) {
                throw x.syntaxError("Misshaped close tag");
            }
            if (stopKey.equals(token) && reachIndex == 0) {
                pathFind = true;
                reachIndex--;
                return true; // Target object found, set foundTarget to true
            }
        return false;
            ......
    }

The function achieves the requirement of stopping the XML parsing as soon as the target object is found by using a combination of the stopKey, reachIndex, and the pathFind flag within a controlled parsing loop. The parse2 function, with its checks and early return, is key to this functionality, allowing the parser to exit as soon as the target is located, thus avoiding the need to read the entire XML file.

  • Add an overloaded static method to the XML class with the signature.
private static final Pattern NUMERIC_PATTERN = Pattern.compile("^[0-9]");

    public static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) {
        JSONObject jo = new JSONObject();
        XMLTokener x = new XMLTokener(reader);
        String[] pathList = path.toString().split("/");
        String replaceKey = pathList[pathList.length - 1];

        if (NUMERIC_PATTERN.matcher(replaceKey).matches()) {
            replaceIndex = Integer.parseInt(pathList[pathList.length - 2]);
        }

        while (x.more()) {
            x.skipPast("<");
            if (x.more()) {
                parse2Replace(x, jo, null, XMLParserConfiguration.ORIGINAL, replaceKey, replacement);
            }
        }

        // Reset global values for future use
        resetGlobalValues();

        return jo;
    }

    private static boolean replacePathFind = false;
    private static int replaceIndex = -1;
    private static boolean hasReplaced = false;

    private static void resetGlobalValues() {
        replacePathFind = false;
        replaceIndex = -1;
        hasReplaced = false;
    }

It reads an XML file into a JSONObject and replaces a sub-object on a certain key path with another JSON object that you construct.

I implement 'parse2Replace' function to process and parse the XML data.

private static boolean parse2Replace(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, String replaceKey, JSONObject replacement) {
        ......
        if (replacePathFind && !hasReplaced) {
            context.remove(replaceKey);
            context.put(replaceKey, replacement.get(replaceKey));
            hasReplaced = true;
        }

        token = x.nextToken();
        ......
}

It helps identify the specific XML element specified by the JSONPointer and performs the replacement operation if needed.

It's my understanding that to replace the method effectively, it's required to process the entire XML file in order to acquire the complete JSONObject.

Testing

  • Run the test file M2Test to see their functionalities Change the directory to /src/test compile the Java file and execute.
javac -cp ../main/java/json-java.jar M2Test.java
java -cp ".;../main/java/json-java.jar" M2Test

You will see the console output:

<?xml version="1.0" encoding="UTF-8"?>
<contact>
  <nick>Crista </nick>
  <name>Crista Lopes</name>
  <address>
    <street>Ave of Nowhere</street>
    <zipcode>92614</zipcode>
  </address>
</contact>

static JSONObject toJSONObject(Reader reader, JSONPointer path)
JSONPointer("/contact/address")
{"zipcode":92614,"street":"Ave of Nowhere"}
-----------------------
static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement)
Given replacement: {"street":"Ave of the Arts"}
JSONPointer("/contact/address/street")
After Replacement:
{"contact":{"nick":"Crista","address":{"zipcode":92614,"street":"Ave of the Arts"},"name":"Crista Lopes"}}

Junit Test

  • I created the XML2Test.java testing file under the test dir, you can run mvn clean test -Dtest=XML2Test in the root dir to perform tests.

  • Inside the testing file, toJSONObject1Test1() is testing the basic functionality.

             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <nick>Crista </nick>\n" +
                "  <name>Crista Lopes</name>\n" +
                "  <address>\n" +
                "    <street>Ave of Nowhere</street>\n" +
                "    <zipcode>92614</zipcode>\n" +
                "  </address>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/address");
JSONObject jo = toJSONObject(reader, path);
            ......
  • toJSONObject1Test2() is testing another xmlString.
             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <person>\n" +
                "    <nick>Crista</nick>\n" +
                "    <name>Crista Lopes</name>\n" +
                "    <address>\n" +
                "      <location>\n" +
                "        <street>Ave of Nowhere</street>\n" +
                "        <zipcode>92614</zipcode>\n" +
                "      </location>\n" +
                "    </address>\n" +
                "  </person>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/person/address");
JSONObject jo = toJSONObject(reader, path);
            ......
  • toJSONObject1Test3() is testing for the wrong JSONPointer (Error).
             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <nick>Crista </nick>\n" +
                "  <name>Crista Lopes</name>\n" +
                "  <address>\n" +
                "    <street>Ave of Nowhere</street>\n" +
                "    <zipcode>92614</zipcode>\n" +
                "  </address>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/street");
JSONObject jo = toJSONObject(reader, path);
            ......
  • toJSONObject2Test1() is testing the basic functionality.
            ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <nick>Crista </nick>\n" +
                "  <name>Crista Lopes</name>\n" +
                "  <address>\n" +
                "    <street>Ave of Nowhere</street>\n" +
                "    <zipcode>92614</zipcode>\n" +
                "  </address>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/address/street");
JSONObject replacement = toJSONObject("<street>Ave of the Arts</street>\n");
JSONObject jo = toJSONObject(reader, path, replacement);
             ......
  • toJSONObject2Test2() is testing to replace the tag.
              ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <nick>Crista </nick>\n" +
                "  <name>Crista Lopes</name>\n" +
                "  <address>\n" +
                "    <street>Ave of Nowhere</street>\n" +
                "    <zipcode>92614</zipcode>\n" +
                "  </address>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/address/street");
JSONObject replacement = toJSONObject("<avenue>Ave of the Arts</avenue>\n");
JSONObject jo = toJSONObject(reader, path, replacement);
            ......
  • toJSONObject2Test3() is testing another xmlString.
             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <person>\n" +
                "    <nick>Crista</nick>\n" +
                "    <name>Crista Lopes</name>\n" +
                "    <address>\n" +
                "      <location>\n" +
                "        <street>Ave of Nowhere</street>\n" +
                "        <zipcode>92614</zipcode>\n" +
                "      </location>\n" +
                "    </address>\n" +
                "  </person>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/person/nick");
JSONObject replacement = toJSONObject("<nick>Professor</nick>\n");
JSONObject jo = toJSONObject(reader, path, replacement);
             ......
  • toJSONObject2Test4() is testing a mismatched tag name. (Error)
             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <nick>Crista </nick>\n" +
                "  <name>Crista Lopes</name>\n" +
                "  <address>\n" +
                "    <street>Ave of Nowhere</street>\n" +
                "    <zipcode>92614</zipcode>\n" +
                "  </address>\n" +
                "</contact>";

Reader reader = new StringReader(xmlString);
JSONPointer path = new JSONPointer("/contact/address/street");
JSONObject replacement = toJSONObject("<tag>Ave of the Arts</street>\n");
JSONObject jo = toJSONObject(reader, path, replacement);
             ......
  • When you run mvn clean test -Dtest=XML2Test in the root dir, you should see Tests run: 7, Failures: 0, Errors: 0, Skipped: 2, because I set @Ignore for the failure cases.
Screenshot 2024-02-02 231642

MileStone2

  • Add an overloaded static method to the XML class with the signature
public static JSONObject toJSONObject(Reader reader, Function<String, String> keyTransformer) {
        JSONObject jsonObject = new JSONObject();
        XMLTokener tokener = new XMLTokener(reader);
        if (keyTransformer == null) {
            return null;
        }
        while (tokener.more()) {
            tokener.skipPast("<");
            if (tokener.more()) {
                parse3(tokener, jsonObject, null, XMLParserConfiguration.ORIGINAL, keyTransformer);
            }
        }
        return jsonObject;
    }

It reads an XML file into a JSON object and adds the prefix "swe262_" to all of its keys, but in a much more general manner, for any transformations of keys. For example:

"foo" --> "swe262_foo" 
"foo" --> "oof"

I implement 'parse3' function with the integration of the keyTransformer function.

private static boolean parse3(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config,  Function keyTransformer) {
        ......
        if (x.next() == '[') {
                        string = x.nextCDATA();
                        if (string.length() > 0) {
                            context.accumulate((String) keyTransformer.apply(config.getcDataTagName()), string);
                        }
                        return false;
                    }

        token = x.nextToken();
        ......
}

Integrating the keyTransformer ensures that the keyTransformer is invoked each time the parse3 function attempts to utilize the accumulate method for enlarging the result. Every occurrence of a Tag or TagName transforms the keyTransformer function, ensuring uniform application throughout the parsing process.

Junit Test

  • I created three more test cases inside the XML2Test.java testing file under the test dir, you can run mvn clean test -Dtest=XML2Test in the root dir to perform tests

  • toJSONObject3Test1() is testing the basic functionality of toJSONObject(Reader reader, Function<String, String> keyTransformer), adding prefix swe262P_ to every key

             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <nick>Crista </nick>\n" +
                "  <name>Crista Lopes</name>\n" +
                "  <address>\n" +
                "    <street>Ave of Nowhere</street>\n" +
                "    <zipcode>92614</zipcode>\n" +
                "  </address>\n" +
                "</contact>";

  StringReader reader = new StringReader(xmlString);
  Function<String, String> func = x -> "swe262P_" + x;
  JSONObject jsonObject = toJSONObject(reader, func);
  System.out.println(jsonObject.toString(2));
            ......
  • toJSONObject3Test2() is testing another xmlString, and add sufix _swe262p to every key.
             ......
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<contact>\n" +
                "  <person>\n" +
                "    <nick>Crista</nick>\n" +
                "    <name>Crista Lopes</name>\n" +
                "    <address>\n" +
                "      <location>\n" +
                "        <street>Ave of Nowhere</street>\n" +
                "        <zipcode>92614</zipcode>\n" +
                "      </location>\n" +
                "    </address>\n" +
                "  </person>\n" +
                "</contact>";

  StringReader reader = new StringReader(xmlString);
  Function<String, String> func = x -> x + "_swe262P";
  JSONObject jsonObject = toJSONObject(reader, func);
  System.out.println(jsonObject.toString(2));
            ......
  • toJSONObject3Test3() is testing to read an XML file and converting every key to upper case.
             ......
File file = new File("./src/Sample1.xml");
        FileReader fr = new FileReader(file);
        BufferedReader br = new BufferedReader(fr);

        Function<String, String> func = String::toUpperCase;

        JSONObject jobj = toJSONObject(br,func);
        System.out.println(jobj.toString(2));
            ......
  • When you run mvn clean test -Dtest=XML2Test in the root dir, you can check each test case's printed result.
Screenshot 2024-02-14 174445

Performance

Before, the codes first had to read through the XML to turn it into JSON. Then, the client had to go through it again to change the keys. Now, by using Functions and lambda expressions, I managed to do the job with just one pass through the XML, which made everything faster. Modifying the accumulate function by calling the keyTransformer function when the parse3 function wants to extend the result

 

Overall, the keyTransformer facilitates flexibility in how the resulting JSON object is structured by enabling dynamic modification of tag names during the parsing process.

MileStone3

  • Add streaming methods to the library that allows the client code to chain operations on JSON nodes. For example:
// in client space
JSONObject obj = XML.toJSONObject("<Books><book><title>AAA</title><author>ASmith</author></book><book><title>BBB</title><author>BSmith</author></book></Books>");
obj.toStream().forEach(node -> do some transformation, possibly based on the path of the node);
List<String> titles = obj.toStream().map(node -> extract value for key "title").collect(Collectors.toList());
obj.toStream().filter(node -> node with certain properties).forEach(node -> do some transformation);
  • I created a new class called JSONNode for implementing the new toStream method in the JSONObject.java file.
public class JSONNode {
    private String path;
    private String key;
    private Object value;

    public JSONNode(String path, String key, Object value) {
        this.path = path;
        this.key = key;
        this.value = value;
    }
    ......
  • For implementing the toStream method, I write a method called addNodes as the helper method.
public Stream<JSONNode> toStream() {
        List<JSONNode> nodes = new ArrayList<>();
        addNodes("/", this, nodes, "");
        return nodes.stream();
    }

    private void addNodes(String path, Object json, List<JSONNode> nodes, String lastKey) {
        if (json instanceof JSONObject) {
            JSONObject jsonObj = (JSONObject) json;
            for (String key : jsonObj.keySet()) {
                Object value = jsonObj.get(key);
                String newPath = path + key;
                JSONNode node = new JSONNode(newPath, key, value);
                nodes.add(node);
                if (value instanceof JSONObject || value instanceof JSONArray) {
                    addNodes(newPath + "/", value, nodes, key);
                }
            }
        } else if (json instanceof JSONArray) {
            JSONArray jsonArray = (JSONArray) json;
            for (int i = 0; i < jsonArray.length(); i++) {
                Object value = jsonArray.get(i);
                String newPath = path + i;
                JSONNode node = new JSONNode(newPath, lastKey.isEmpty() ? String.valueOf(i) : lastKey, value);
                nodes.add(node);
                if (value instanceof JSONObject || value instanceof JSONArray) {
                    addNodes(newPath + "/", value, nodes, lastKey);
                }
            }
        }
    }

The addNodes function effectively explores the entire JSON structure, creating a JSONNode for each key-value pair encountered, and populating the nodes list. Then, the toStream function simply returns a stream of JSONNode objects created from the populated list.

Junit Test

  • I created a new test file called MileStone3Test for testing toStream method's functionalities.
obj.toStream().forEach(node -> do some transformation, possibly based on the path of the node);
List<String> titles = obj.toStream().map(node -> extract value for key "title").collect(Collectors.toList());
obj.toStream().filter(node -> node with certain properties).forEach(node -> do some transformation);
  • When you run mvn clean test -Dtest=MileStone3Test in the root dir, you can check each test case's printed result.
截圖 2024-02-28 下午5 55 07

MileStone4

  • Add asynchronous methods to the library that allow the client code to proceed, while specifying what to do when the JSONObject becomes available. This is useful for when reading very large files. For example:
XML.toJSONObject(aReader, (JSONObject jo) -> {jo.write(aWriter);}, (Exception e) -> {/* something went wrong */});

I created a private class inside the `XML.java` called `FutureJsonObject` for implementing the `toJSONObject`

    public static Future<JSONObject> toJSONObject(Reader reader, Function<String, String> keyTransformer, Consumer<Exception> exceptionHandler) {
            ......
        try {
            if (keyTransformer == null)
                throw new Exception();
            future = new FutureJsonObject();
            futureObject = future.toJSONObject(reader, keyTransformer);

            // shutdown executor when future toJSONObject is done
            if (futureObject.isDone())
            ......
    }
    
    private static class FutureJsonObject {
            ......

        public Future<JSONObject> toJSONObject(Reader reader, Function keyTransformer) throws Exception{
            return executor.submit(() -> {
                System.out.println("[FutureJsonObject]");
                return XML.toJSONObject(reader, keyTransformer);
            });
        }

            ......
    }

The FutureJsonObject class encapsulates the details of performing an asynchronous operation for converting XML to JSON with key transformation and resource management. The toJSONObject static function acts as an interface to this functionality, handling initial checks, exception handling, and ensuring proper cleanup after the operation.

Junit Test

  • I created a new test file called MileStone4Test for testing toJSONObject method's functionalities.

  • Inside the testing file, testTransformJSONObjectKeyAsyn verify that the XML to JSON conversion correctly applies the key transformation function to all keys in the resulting JSON object.

public void testTransformJSONObjectKeyAsyn(){
        try {
            FileReader reader = new FileReader("src/test/java/org/json/junit/data/exampleXML.xml");
            Function<String, String> keyTransformer = (key) -> "swe262_" + key;
            Consumer<Exception> exceptionHandler = (e) -> e.printStackTrace();

            Future<JSONObject> futureActual = XML.toJSONObject(reader, keyTransformer, exceptionHandler);
            String expect = "{\n" +
                    "  \"swe262_library\": {\n" +
                    "    \"swe262_book\": [\n" +
                   
            ......
  • testTransformJSONObjectKeyAsynReturnType confirm that the method returns a JSONObject as its result when the conversion and transformation are successful.
public void testTransformJSONObjectKeyAsynReturnType() {
        try {
            FileReader reader = new FileReader("src/test/java/org/json/junit/data/exampleXML.xml");
            Function<String, String> keyTransformer = (key) -> "swe262_" + key;
            Consumer<Exception> exceptionHandler = (e) -> e.printStackTrace();

            Future<JSONObject> futureActual = XML.toJSONObject(reader, keyTransformer, exceptionHandler);

            ......
  • testTransformJSONObjectKeyAsynExceptionHandler test the method's exception handling capability when provided with invalid inputs or conditions that trigger an exception.
public void testTransformJSONObjectKeyAsynExceptionHandler() {
        try {
            FileReader reader = new FileReader("src/test/java/org/json/junit/data/exampleXML.xml");
            Consumer<Exception> exceptionHandler = (e) -> System.out.println("OMG ERROR!!");

            Future<JSONObject> futureActual = XML.toJSONObject(reader, null, exceptionHandler);
            assertNull(futureActual);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
  • When you run mvn clean test -Dtest=MileStone4Test in the root dir, you can check each test case's printed result.

About

No description, website, or topics provided.

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages