{"id":13694585,"url":"https://github.com/americanexpress/unify-jdocs","last_synced_at":"2026-03-05T23:18:05.509Z","repository":{"id":42703485,"uuid":"265619935","full_name":"americanexpress/unify-jdocs","owner":"americanexpress","description":"A new way of working with JSON documents without using model classes or JSON schemas","archived":false,"fork":false,"pushed_at":"2024-11-05T23:20:04.000Z","size":623,"stargazers_count":75,"open_issues_count":0,"forks_count":11,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-03-31T06:08:41.251Z","etag":null,"topics":["java","json"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/americanexpress.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-20T16:13:33.000Z","updated_at":"2024-12-30T22:25:06.000Z","dependencies_parsed_at":"2023-11-14T00:29:06.828Z","dependency_job_id":"5fc5394c-c6ff-4bbb-81fc-42d6095875e6","html_url":"https://github.com/americanexpress/unify-jdocs","commit_stats":null,"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/americanexpress%2Funify-jdocs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/americanexpress%2Funify-jdocs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/americanexpress%2Funify-jdocs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/americanexpress%2Funify-jdocs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/americanexpress","download_url":"https://codeload.github.com/americanexpress/unify-jdocs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247608153,"owners_count":20965952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","json"],"created_at":"2024-08-02T17:01:35.195Z","updated_at":"2026-03-05T23:18:00.474Z","avatar_url":"https://github.com/americanexpress.png","language":"Java","readme":"## JDocs - A new way of working with JSON documents\n\n---\nJDocs (JSON Documents) is a JSON manipulation library.\nIt completely eliminates the need to have model / POJO classes and instead works directly\non the JSON document. Once you use this library, you may never \nwant to return to using Java model classes or using JSON schema for JSON document validation.\n\n---\n\n#### Getting JDocs package\n\nJDocs is available as a jar file in Maven central with the following latest Maven coordinates.\n\n````pom\n\u003cgroupId\u003ecom.americanexpress.unify.jdocs\u003c/groupId\u003e\n\u003cartifactId\u003eunify-jdocs\u003c/artifactId\u003e\n\u003cversion\u003e1.7.1\u003c/version\u003e\n````\n\n---\n\n#### Primer on model classes, marshalling and unmarshalling\n\nWhen JSON is used in applications (either to store data or as a means to exchange data between\napplications / systems), the JSON text document is converted into language specific data structures.\nFor the Java programming language, this consists of Java classes and objects.\n\nFor the following JSON:\n\n```json\n{\n   \"first_name\": \"Deepak\",\n   \"last_name\": \"Arora\",\n   \"phones\": [\n     {\n     \"type\": \"home\",\n     \"number\": \"0134578965\"\n     },\n     {\n     \"type\": \"mobile\",\n     \"number\": \"04455678965\"\n     }\n   ]\n }\n```\n\nthe following Java classes would be created:\n\n```java\npublic class Person {\n  String first_name;\n  String last_name;\n  Phone[] phones;\n}\n\npublic class Phone {\n  String type;\n  String number;\n}\n```\n \nThese Java classes created are referred to as model classes. The JSON document is parsed and converted into\nJava objects which are then used in the program. The process of converting JSON documents into \nlanguage specific objects is known as ‘unmarshalling\" and of converting the language specific objects\nback into JSON documents as \"marshalling\" i.e.\n\n_Marshalling -\u003e Java object to JSON document_\n\n_Unmarshalling -\u003e JSON document to Java object_\n\n---\n\n#### Challenges faced in using model classes to work with JSON documents\n\nIn an application program, model classes, marshalling and unmarshalling is extensively used to convert \nJSON into Java objects and vice versa. This approach has the following challenges associated with it:\n\n1. The JSON to Java mapping and vice versa has to be created and maintained. Any time the JSON document\nstructure changes, these classes must also be updated as also the programs using these classes.\nHaving model objects creates an additional layer which always needs to be kept in sync with the JSON text\ndocument structure. The complexity is compounded by the fact that JSON documents can be arbitrary levels\ndeep in which case keeping them in sync becomes ever more challenging. This also tightly couples the JSON\ndocument, model classes and the business logic code making the application difficult to change\n2. The problem of code bloat. Typically applications deal with multiple JSON document types.\nEach JSON document type may map to multiple Java classes. This situation leads to a plethora of\nJava classes and wrapper functions written to access fields in these classes. Over time\nthis leads to code bloat consisting of numerous Java classes with limited value except for reading and writing JSON\nelements. Also, accessing nested fields / arrays may requires multiple lines of code as traversal of\nfields needs to be done across levels and null values / absent entries dealt with\n\nThe consequences over time of the above are:\n1. Inability to carry out a fast, accurate and exhaustive impact analysis. For example, where all in the code base is this json path used?\n2. Changing JSON document structure becomes extremely difficult, tedious and error prone. In large code bases,\nit becomes next to impossible\n3. There is an adverse impact on performance leading to higher usage of system resources\n4. The code comprehension and readability suffers leading to maintainability issues\n5. Finally deterioration in quality, longer turnaround time for changes, higher efforts and\nultimately higher costs and risks\n\n---\n\n#### How does JDocs address these challenges?\n\nJDocs has been designed to completely do away with model classes and provide advanced features for\nmanipulating JSON documents directly through JSON paths (with a slight home grown variation).\n\nDoing away with model classes has the following benefits:\n\n1. Reduces the amount of code by ~90% which in turns means significantly faster implementations,\nreduced effort, improved quality and faster time to market\n2. Helps developers concentrate on implementing business logic rather than spending time and effort on\nmanipulating model classes to access data\n3. Simplifies the way code is written and makes it easily comprehensible.\nTo know which elements are being accessed, developers no longer need to go through lines and lines of\ncode that only deal with traversing model classes and have very little to do with business logic\n4. Allows for fast, accurate and exhaustive impact analysis across the code base. Developers can in a matter of\nseconds locate all usages of a json path across the code base\n5. By always referring to data as JSON paths, it allows developers to gain a much better understanding of\nthe business domain, data and its usage\n\n---\n\n#### JDocs in Action\n\nLets start with a sample JSON document as below:\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"last_name\": \"Arora\",\n  \"is_married\": true,\n  \"number_of_children\": 2,\n  \"home_address\": {\n    \"line_1\": \"XYZ, Greenway Pkwy, #ABC\",\n    \"zip\": \"85254\"\n  }\n}\n```\n \n##### Reading and writing elements\n\nThe first step is to get a `Document` by so:\n\n```java\nString json = \"\u003ccontents of the example json file\u003e\"; \nDocument d = new JDocument(json);\n```\n\nReading and writing can be done using get and set methods of the API:\n\n```java\nString s = d.getString(\"$.first_name\"); // will return Deepak\nBoolean b = d.getBoolean(\"$.is_married\"); // will return true\nInteger i = d.getInteger(\"$.number_of_children\"); // will return 2\ns = d.getString(\"$.home_address.line_1\"); // will return \"XYZ, Greenway Pkwy, #ABC\"\n```\n\nSimilarly, set methods of the API are used to set values directly in the JSON document.\nLet’s say you execute the following commands:\n\n```java\nd.setString(\"$.first_name\", \"John\");\nd.setString(\"$.last_name\", \"Ryan\");\nd.setString(\"$.middle_name\", \"Smith\");\nd.setString(\"$.is_married\", false);\nd.setInteger(\"$.number_of_children\", 0);\nd.setString(\"$.home_address.zip\", \"85032\");\nString s = d.getPrettyPrintJson();\n```\n \nThe value of `s` will now be:\n```json\n{\n  \"first_name\": \"John\",\n  \"middle_name\": \"Smith\",\n  \"last_name\": \"Ryan\",\n  \"is_married\": false,\n  \"number_of_children\": 0,\n  \"home_address\": {\n    \"line_1\": \"XYZ, Greenway Pkwy, #ABC\",\n    \"zip\": \"85032\"\n  }\n}\n```\n\nOne could also use the following method to get a compressed JSON document:\n\n```java\nString s = d.getJson();\n```\n\nSee that the data element \"middle_name\" which was not existing earlier has been created.\nThe elements which already existed at the path specified have been updated.\nYou can create any arbitrary path in the document by specifying that path in the API method.\nThis can be used to create complex objects or objects inside objects.\nConsider an empty JSON document on which the following commands are run:\n\n```java\nDocument d = new JDocument();\nd.setString(\"$.person.first_name\", \"John\");\nd.setString(\"$.person.last_name\", \"Ryan\");\nd.setString(\"$.person.middle_name\", \"Smith\");\nd.setString(\"$.person.address.city\", \"Phoenix\");\nString s = d.getPrettyPrintJson();\n```\n\nThe value of `s` will be:\n\n```json\n{\n  \"person\": {\n    \"first_name\": \"John\",\n    \"middle_name\": \"Smith\",\n    \"last_name\": \"Ryan\",\n    \"address\": {\n      \"city\": \"Phoenix\"\n    }\n  }\n}\n```\n\nFrom the above, note that complex objects person and person.address have automatically been created.\n\nSo far so good and you may ask whats so special about this? There are libraries available that\nprovide reading and writing of elements using JSON paths. Well, now let's start to make things interesting.\n\n---\n\n##### Reading and writing arrays\n\nConsider the following JSON document. Lets refer to it as snippet 1:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": \"123456\"\n    }\n  ]\n}\n```\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 1\nString s = d.getString(\"$.phones[0].type\"); // will return Home\ns = getString(\"$.phones[0].number\"); // will return 123456\n```\n\nLets make things more interesting! You could refer to the array index by specifying a selection criteria. A\nselection criteria is a simple `field=value` specification inside of the square brackets. It tells JDocs to\nlook for an element in the `phones` array which has a field by the name of type and whose value is \"Home\":\n\n```java\nd.getString(\"$.phones[type=Home].type\"); // will return Home\nd.getString(\"$.phones[type=Home].number\"); // will return 123456\n```\n\nA similar construct could be used to set the contents of an array. Consider the following statements:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 1\nd.setString(\"$.phones[0].number\", \"222222\");\nd.setString(\"$.phones[0].country\", \"USA\");\nd.setString(\"$.phones[1].type\", \"Cell\");\nd.setString(\"$.phones[1].number\", \"333333\");\nd.setString(\"$.phones[1].country\", \"USA\");\nString s = d.getPrettyPrintJson();\n```\n\nWould result in the following value of `s`:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": \"222222\",\n      \"country\": \"USA\"\n    },\n    {\n      \"type\": \"Cell\",\n      \"number\": \"333333\",\n      \"country\": \"USA\"\n    }\n  ]\n}\n```\n\nNote that a new element has been created in the array. The same effect could also have been achieved by the following:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 1\nd.setString(\"$.phones[type=Home].number\", \"222222\")\nd.setString(\"$.phones[type=Home].country\", \"USA\")\nd.setString(\"$.phones[type=Cell].number\", \"333333\")\nd.setString(\"$.phones[type=Cell].country\", \"USA\")\nString s = d.getPrettyPrintJson();\n```\n\nNote that JDocs when it did not find an array element with `type=Cell`, it went ahead and created one.\nBy default, since the value of the field `type` is not being set explicitly, it assumed the field to be of `String` type.\nIf you had not wanted that, you could very well have used something like below:\n \n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 1\nd.setString(\"$.phones[type=Home].number\", \"222222\")\nd.setString(\"$.phones[type=Home].country\", \"USA\")\nd.setInteger(\"$.phones[type=0].type\", 0)\nd.setString(\"$.phones[type=0].number\", \"333333\")\nd.setString(\"$.phones[type=0].country\", \"USA\")\nString s = d.getPrettyPrintJson();\n```\n\nThe above would result in the following value of `s`:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": \"222222\",\n      \"country\": \"USA\"\n    },\n    {\n      \"type\": 0,\n      \"number\": \"333333\",\n      \"country\": \"USA\"\n    }\n  ]\n}\n```\n\nNow for some interesting scenarios. You may ask what if I do the following?\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 1\nd.setInteger(\"$.phones[type=Home].type\", 0);\nString s = d.getPrettyPrintJson();\n```\n\nWell, JDocs will try and search for the element with field type having a value Home and will implicitly\nhandle different data types both for searching and writing. In the case of the above,\neven though the field was stored as `String` type, JDocs converted it to a number.\nThe following will be the value in `s`:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": 0,\n      \"number\": \"123456\"\n    }\n  ]\n}\n```\n\nNow what if you were to do the following?\n\n```java\nDocument d = new JDocument(); // creating an empty document\nd.setInteger(\"$.phones[1].type\", 0); // specifying array index as 1 without the element at 0 being present\nString s = d.getPrettyPrintJson();\n```\n\nIn this case, JDocs would throw an out of bounds exception as below:\n\n```java\ncom.americanexpress.unify.jdocs.UnifyException: Array index out of bounds -\u003e phones\n``` \n\nJDocs realized that an element was being attempted to be added at an index which did not have an element\nat the previous index and hence disallowed that by throwing an exception. In case an array index is specified,\nJDocs will only add an array element if the previous element exists or if the element index has value 0.\nIf however, a selection criteria of the form of `type=value` was specified, and if no element existed\nwhich had a field named type with the given value, it would then have created an element at the end of the array.\n\nYou could use this same notation to create new arrays, create complex objects within arrays,\ncreate arrays within arrays etc. An example of this is below:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 1\nd.setString(\"$.addresses[0].type\", \"Home\")\nd.setString(\"$.addresses[0].line_1\", \"Greenway Pkwy\")\nString s = d.getPrettyPrintJson();\n```\n\nNow `s` will have the following value:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": \"123456\"\n    }\n  ],\n  \"addresses\": [\n    {\n      \"type\": \"Home\",\n      \"line_1\": \"Greenway Pkwy\"\n    }\n  ]\n}\n```\nImportant points to keep in mind about array creation:\n\nIn case array indexes are used:\n1. In case the index exists, the element in that index will be updated\n2. In case the index does not exist and the index value is 0, an array element will be created\n3. In case the index does not exist and the index value is greater than 0, an element\nwill be created only if the index is one greater than the maximum index in the existing array\n\nIn case an array selection criteria is used:\n1. If an element exists which contains the field and value as specified in the selection criteria,\nthat element will be updated\n2. If no element has a field as specified in the selection criteria, a new array element will\nbe created with a field set to the value specified in the criteria\n\n---\n\n##### Iterating arrays\nNow, specifying array indexes hard coded as above is fine. But in the real world, you do not know the number\nof elements that an array may have and so you need a way to find out the number of elements in the\narray and to be able to traverse over all the elements, read them and them and maybe set fields in them\nas you go along.\n\nConsider the following JSON sample. Lets call it snippet 2.\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": \"222222\",\n      \"country\": \"USA\"\n    },\n    {\n      \"type\": \"Cell\",\n      \"number\": \"333333\",\n      \"country\": \"USA\"\n    }\n  ]\n}\n```\n\nYou can find the size of an array as below:\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 2\nint size = d.getArraySize(\"$.phones[]\"); // will contain the value 2\n``` \n\nNote that in the above, empty square brackets was specified with phones. This is required so as\nto inform JDocs that phones is an array in the JSON document.\n\nNow traversing over the array is trivial. But note the technique used closely. You would not want to deviate\nfrom this technique for the following reasons specified below.\n\nCAUTION!!! deviating from this technique will negate one of the most important benefit of JDocs\nwhich is the ability to carry out an exhaustive impact analysis of any JSON path used in the codebase.\nLets see the traversal first. Continuing from the above Java snippet:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 2\nint size = d.getArraySize(\"$.phones[]\"); // will contain the value 2\nfor (int i = 0; i \u003c size; i++) {\n  String index = i + \"\";\n  String type = d.getString(\"$.phones[%].type\", index); \n  String number = d.getString(\"$.phones[%].number\", index); \n  String country = d.getString(\"$.phones[%].country\", index);\n  System.out.println(type);\n  System.out.println(number);\n  System.out.println(country);\n}\n```\n\nwill print:\n```java\nHome\n222222\nUSA\nCell\n333333\nUSA\n```\n\nSetting the elements while traversing is on exactly similar lines as below:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 2\nint size = d.getArraySize(\"$.phones[]\"); // will contain the value 2\nfor (int i = 0; i \u003c size; i++) {\n  String index = i + \"\";\n  d.setString(\"$.phones[%].type\", \"Cell\", index);\n  d.setString(\"$.phones[%].number\", \"111111\", index);\n  d.setString(\"$.phones[%].country\", \"USA\", index);\n}\nfor (i = 0; i \u003c size; i++) {\n  String index = i + \"\";\n  String type = d.getString(\"$.phones[%].type\", index); \n  String number = d.getString(\"$.phones[%].number\", index); \n  String country = d.getString(\"$.phones[%].country\", index);\n  System.out.println(type);\n  System.out.println(number);\n  System.out.println(country);\n}\n```\nwill print:\n```java\nCell\n111111\nUSA\nCell\n111111\nUSA\n```\n\nNow lets discuss why deviating from the above technique is not be a good idea. See the code below:\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 2\nint size = d.getArraySize(\"$.phones[]\"); // will contain the value 2\nfor (int i = 0; i \u003c size; i++) {\n  String index = i + \"\";\n  String type = d.getString(\"$.phones[\" + \n                             index + \"].type\"); \n  System.out.println(type);\n}\n```\n\nNote that while the above code will work, notice two things:\n1. The line containing the JSON path has been split into 2 lines\n2. The construction of the JSON path is now split using +\n\nWhat either of the above points mean is that it is no longer possible in an IDE to be able to use\nregular expression searching for a JSON path in the codebase. Earlier, a regular\nexpression `\"\\$\\.phones\\[.*\\].type\"` could be used to search for all occurrences of this JSON path across\nthe code base but now that ability is lost. To be able to retain this ability is absolutely of crucial\nimportance as that is what one needs when something is changed in the JSON structure. One needs to\nknow where all it is used across the codebase.\n\nFurther more is also reduces code comprehension as the the JSON path is no longer visible\nas a running continuous string. Unfortunately, this is one thing which cannot be controlled using\nautomation and the message just has to be drilled across and reviews conducted on code.\n\nMany of the API methods which take JSON path as input use variable lenght string arguments so as to\nbe able to specify the JSON path as a running continuous string. The % symbol is used between square\nbrackets which is replaced in sequence by the variable arguments.\n\n---\n\n##### Deleting paths in a document\n\nThe API provides a method to delete paths in the document.\nThe path may be a leaf node or may point to a complex object or an array or an array element.\n\nConsider the following JSON snippet 3:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"national_ids\": {\n    \"ssn\": \"2345678\",\n    \"dl_number\": \"DL1234\"\n  },\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": \"222222\",\n      \"country\": \"USA\"\n    },\n    {\n      \"type\": \"Cell\",\n      \"number\": \"333333\",\n      \"country\": \"USA\"\n    }\n  ]\n}\n```\n\nThe method is straightforward as below:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 3\nd.deletePath(\"$.first_name\"); // will delete the first_name field\nd.deletePath(\"$.national_ids\"); // will delete the whole complex object national_ids\nd.deletePath(\"$.phones[]\"); // will delete the whole phones array\nd.deletePath(\"$.phones[0]\"); // will delete the first element of the array\nd.deletePath(\"$.phones[type=Home]\"); // will delete that element of the array which has a type field equal to Home value\n```\n\nThe method also takes variable length argument so as to be able to use it for arrays like so:\n```java\nDocument d = new JDocument(json); // assuming json is a string containing snippet 3\nd.deletePath(\"$.phones[type=%]\", \"Home\"); // will delete that element of the array which has a type field equal to Home value\nd.deletePath(\"$.phones[%]\", 0 + \"\"); // will delete the first element\n```\n\n---\n\n##### The concept of Base and Typed Documents\n\nTill now we talked of operations which can be done on a free form JSON document meaning that it is \npossible to read and write pretty much any path we feel like. But what if we wanted to lock down the structure of a JSON\ndocument? Typically we would use something like JSON schema. We thought that there was a far simpler and \nmore intuitive way to do the same thing. This is where we now start to talk of Base and Typed documents.\n\nIn JDocs, there can be two types of documents. A Base document which we have worked with so far\nabove and a Typed document. A Base document is one that has a free structure i.e.\nany element can be written at any path using the API.\nA Typed document is one that has a defined structure that needs to be adhered to.\nWe define this structure in something called as a Model document.\nIn other words, a Model document locks the structure of a JSON document to a specific one.\nFurther, for each leaf element, a Model also defines the constraints in terms of field type, format etc.\nEvery typed document is associated with a model document.\n\nA model documents is also a JSON document that contains all valid paths for a data document.\nAgainst each leaf node path, it specifies the constraints applicable for that element.\n\nIn case of arrays, the model document contains only one element and constraints defined\nin this one element are applicable for all elements of the array in the data document. While\nit is true that JSON documents can have different data types for the same field across elements, but\nif you really think about it, for the purpose of defining a structure, each element really\nneeds to be of the same type.\n\nLets understand this using an example. Consider the snippet 4 below:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"start_date\": \"28-Jan-2020 21:18:10 America/Phoenix\",\n  \"id\": 12345,\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": 111111\n    },\n    {\n      \"type\": \"Cell\",\n      \"number\": 222222\n    }\n  ]\n}\n```\n\nThe model for the above JSON document would be defined as below:\n\n```json\n{\n  \"first_name\": \"{\\\"type\\\":\\\"string\\\"}\",\n  \"start_date\": \"{\\\"type\\\":\\\"date \\\"format\\\":\\\"dd-MM-yyyy HH:mm:ss VV\",\n  \"id\": \"{\\\"type\\\":\\\"integer\\\", \\\"regex\\\":\\\".*\\\"}\",\n  \"phones\": [\n    {\n      \"type\": \"{\\\"type\\\":\\\"string\\\"}\",\n      \"number\": \"{\\\"type\\\":\\\"integer\\\", \\\"regex\\\":\\\".*\\\"}\"\n    }\n  ]\n}\n```\n\n---\n\n**Loading model documents**\n\nIt is mandatory for a model to be loaded before a typed document of that type is created.\nModel documents can be loaded using the following:\n\n```java\nJDocument.loadDocumentTypes(type, json);\n``` \n\n`String type` specifies the type of the document\n`String json` specifies the content of the model document\n\n---\n\n**Creating typed documents**\n\n```java\nDocument d = new JDocument(\"model\", null);\n```\n\nThe above creates an empty document which is tied to a model document named as `model`\n(expected to be already loaded as described in the above section).\n\n```java\nString s = \"...\"; // s contains the contents of a JSON document\nDocument d = new JDocument(\"model\", s);\n```\n\nThe above creates a typed `JDocument` from an existing JSON document stored in the string `s`.\nJDocs, while loading this document, will run the validations on the document against the model\nand if the structure / constraints do not match, the appropriate exception will be thrown.\n\nAlso, when writes to the document are done using setXXX methods,\nthe structure and constraints will be validated against the model.\nFor example, for snippet 4 above, the following calls will succeed as the paths and the\nconstraints on the elements are all valid:\n\n```java\nDocument d = new JDocument(\"model\", null);\nd.setString(\"$.first_name\", \"Deepak1\");\nd.setString(\"$.phones[0].type\", \"Home\");\nd.setInteger(\"$.phones[0].number\", 333333);\n```\n\nWhereas the following will fail:\n```java\nd.setString(\"$.first_name1\", \"Deepak1\") // incorrect path\nd.setString(\"$.start_date\", \"15-Apr-2019\") // incorrect date format\nd.setString(\"$.phones[0].number\", \"111111\") // incorrect data type\n```\n\nAs regards constraints, the validation is done against the specified regular expression. If a match returns true, the\nvalidation is assumed to succeed else an exception is thrown.\n\nThe following attributes are implemented as part of specifying a constraint on a path:\n\nS. No. | Field Name | Description | Type | Mandatory?\n------ | --------------- | ------------| ---- | ----------\n1 | type | Defines the type of the field. Possible values are string, integer, long, decimal, boolean, date | String | Yes\n2 | regex | The pattern against which the value will be validated | string | No\n3 | null_allowed | Specifies if null is a valid value for the field. If not specified, default is null not allowed. If value is null and allowed, regex will be ignored | boolean | No\n4 | ignore_regex_if_empty_string | If not specified, default is false. If specified to true, regex will be ignored if value is empty. Applicable only for string type fields | boolean | No\n5 | format | Only applicable for date type. Specification is as per DateTimeFormatter | string | Yes\n6 | empty_date_allowed | Only applicable for date type fields. If not specified, default is true. If allowed format check is ignored  | boolean | No\n\n**Validating typed documents**\n\nTyped documents may be validated when:\n\n1. They are constructed\n2. At the time of specifying the type of a base document (`setType` method)\n3. When calling the `validateAllPaths` or `validateModelPaths` methods\n4. When calling the `getXXX` or `setXXX` methods on the document e.g. `getString` / `setString`\n\nThere are three types of validations mechanism that can be specified:\n\n`CONSTS_JDOCS.VALIDATION_TYPE.ALL_DATA_PATHS`\n\nAll paths found in the document will be validated against the model document.\n\n`CONSTS_JDOCS.VALIDATION_TYPE.ONLY_MODEL_PATHS`\n\nOnly those data paths for which model paths exists will be validated against the model document.\n\n`CONSTS_JDOCS.VALIDATION_TYPE.ONLY_AT_READ_WRITE`\n\nValidations will be performed only when a data path is read or written by the use of\n`getXXX` and `setXXX` methods.\n\nThe validation types `CONSTS_JDOCS.VALIDATION_TYPE.ONLY_MODEL_PATHS`\nand `CONSTS_JDOCS.VALIDATION_TYPE.ONLY_AT_READ_WRITE`\nare used in scenarios where we may be getting extra fields / paths from an external entity (think an API response) and\nthese paths are not part of our model document. In such cases, we can look to validate the paths which are found in the\nmodel document or validate them only at read and write of paths.\n\nThis is a typical scenario in the use of APIs which can return extra blocks and paths which may not be present in the\nmodel document and which we may not be interested in. If we were to validate at the time of creating the document, the\nmodel validations would fail unless we kept the model document in sync with all the changes happening on the API side.\nMost of the times, this is not possible as the teams are separate and there is no reason that adding fields to the\nresponse which we are not interested in should cause a failure.\n\nThe validation mechanism can be set at the whole of JDocs level or at a per document level. To set the validation\nmechanism at a JDocs level, initialize JDocs by specifying the validation type in the `init` method as below. This\nvalidation type will come into effect for all typed documents unless a validation type has been specified explicitly for\na document:\n\n`public static void init(CONSTS_JDOCS.VALIDATION_TYPE validationType)`\n\nIf the `init` method is called without any parameters, then `CONSTS_JDOCS.VALIDATION_TYPE.ALL_DATA_PATHS` is assumed\ndefault.\n\nWe can also specify the validation type independently at the document level. Assuming that we have called the default\n`init` method (with no parameters), lets say that for only one document in a particular instance, we wanted to use\n`CONSTS_JDOCS.VALIDATION_TYPE.ONLY_MODEL_PATHS` validation mechanism. We could override JDocs default validation\nmechanism by specifying the validation while constructing the document as below:\n\n`Document d = new JDocument(type, json, CONSTS_JDOCS.VALIDATION_TYPE.ONLY_MODEL_PATHS);`\n\nor while setting the type of a base document as below:\n\n`d.setType(type, CONSTS_JDOCS.VALIDATION_TYPE.ONLY_MODEL_PATHS);`\n\nThere are also a couple of utility methods to validate a document against a model as below:\n\n`public void validateAllPaths(String type);`\n\n`public void validateModelPaths(String type);`\n\n---\n\n##### Fetching content from a document\n\nGiven a document, it is possible to extract content from it as a new document. Consider the snippet below:\n\n```json\n{\n   \"id\": \"id\",\n  \"family\" : {\n    \"number_of_members\": 2,\n    \"members\": [\n      {\n        \"name\": \"Deepak Arora\",\n        \"phones\": [\n          {\n            \"type\": \"Home\",\n            \"number\": \"1111111111\"\n          },\n          {\n            \"type\": \"Cell\",\n            \"number\": \"2222222222\"\n          }\n        ]\n      },\n      {\n        \"name\": \"Nitika Kaushal\",\n        \"phones\": [\n          {\n            \"type\": \"Home\",\n            \"number\": \"3333333333\"\n          },\n          {\n            \"type\": \"Cell\",\n            \"number\": \"4444444444\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nWe can extract content like below:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing above snippet\nDocument d1 = d.getContent(\"$.family.members[1].phones[0]\", false, true);\nString s = d1.getPrettyPrintJson();\n```\n\nThe value of `s` will contain:\n\n```json\n{\n  \"family\" : {\n    \"members\": [\n      {\n        \"phones\": [\n          {\n            \"type\": \"Home\",\n            \"number\": \"3333333333\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nNote that the third parameter (`includeFullPath`) is specified as `true`. This means that return the full path in the document. Had we set\nthat to `false`, the following would have been returned:\n\n```json\n{\n    \"type\": \"Home\",\n    \"number\": \"3333333333\"\n}\n```\n\nThe path specified in this method has to point to either:\n* a complex object\n* an array element (which also needs to be a complex object)\n* an array (like `phones[]`). In this case, the `includeFullPath` parameter has to be false else the API\nwill throw an exception\n\nIn case the document we are extracting content from is a `TypedDocument`, then we have the option of returning\na `BaseDocument` or a `TypedDocument`. This is specified using the second parameter `returnTypedDocument`. If this\nparameter is set to `true` and if the document from which content is being extracted is a `TypedDocument`, then\nthe return document will also be a `TypedDocument` of the same type. In case the document we are extracting content from\nis not a `TypedDocument`, this parameter is ignored.\n\nOf course, if a `TypedDocument` is being returned, its needs to conform to the structure of the model document. In\nthis situation, if we specify `includeFullPath = false`, it is possible that the returned document when constructed\nwill not conform to the model document in which case the API will throw an exception. \n\nAs with other methods in the API, the path can contain `%` and the value specified in the last variable arguments parameter.\n\n---\n\n##### Copying content across documents\n\nGiven two Jdoc documents, it is possible to copy content from one document to another.\nUsing this functionality, one can copy complex objects from one document to another.\nA complex object means any object in a document that is not a leaf node i.e.\nit itself contains objects. A complex node may also be an array or an element of an array.\nFor reading and writing leaf elements, the get and set API is meant to be used.\n\nCopy contents is also applicable on typed documents, in which case, all \"from\" paths and their values are\nvalidated against the model and constraints and exceptions thrown in case of errors encountered.\n\nConsider the following as snippet 5:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": 111111\n    }\n  ]\n}\n```\n\nand the following as snippet 6:\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"addresses\": [\n    {\n      \"type\": \"Home\",\n      \"number\": 111111\n    }\n  ]\n}\n```\n\nThe following is am example of copying content:\n```java\nDocument to = new JDocument(json); // assuming json is a string containing snippet 5\nDocument from = new JDocument(json1); // assuming json1 is a string containing snippet 6\nto.setContent(from, \"$.addresses[]\", \"$.addresses[]\");\nString s = to.getPrettyPrintJson();\n```\n\nThe string `s` will contain the following:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"type\": \"Home\",\n      \"number\": 111111\n    }\n  ],\n  \"addresses\": [\n    {\n      \"type\": \"Home\",\n      \"number\": 111111\n    }\n  ]\n}\n```\n\n---\n\n##### The concept of array keys\n\nFor typed documents, JDocs implements a unique concept of array keys.\nAn array key is a field in the array element which is unique across all elements in the array.\nThink of it as an array having this field as its primary key.\nAlso, this key field is expected to be present in all elements in the array.\n\nConsider the following snippet 6:\n\n```json\n{\n  \"first_name\": \"Deepak\",\n  \"phones\": [\n    {\n      \"phone_type\": \"Home\",\n      \"number\": \"222222\",\n      \"country\": \"USA\"\n    },\n    {\n      \"phone_type\": \"Cell\",\n      \"number\": \"333333\",\n      \"country\": \"USA\"\n    }\n  ]\n}\n```\n\nHere, the type field in each of the elements of the phones array can be used as an array key.\nThis field is defined in the model document using the reserved keyword \"jdocs_arr_pk\" as below:\n\n```json\n{\n  \"first_name\": \"{\\\"type\\\":\\\"string\\\"}\",\n  \"phones\": [\n    {\n      \"jdocs_arr_pk\": \"{\\\"field\\\":\\\"phone_type\\\"}\",\n      \"phone_type\": \"{\\\"type\\\":\\\"string\\\"}\",\n      \"number\": \"{\\\"type\\\":\\\"string\\\"}\",\n      \"country\": \"{\\\"type\\\":\\\"string\\\"}\"\n    }\n  ]\n}\n```\n\nHence, for elements in arrays in typed documents, \"jdocs_arr_pk\" becomes a reserved value and cannot be used\nto define a field name.\n\nThe concept of array key fields is used while merging typed documents into one another as\ndescribed in the next section. This is one of the most powerful features of JDocs.\n\n---\n\n##### Merging documents\n\nUsing this feature, you can merge the contents of one typed document into another. For discussing the\nconcept of merging, a \"from\" document and a \"to\" document is used. Both \"from\" and \"to\"\ndocuments need to be typed documents having the same model.\n\nLets take snippet 6 as the starting point for understanding. Consider the following code:\n\n```java\nDocument to = new JDocument(\"model\", fromJson); // fromJson contains snippet 6 json\nDocument from = new JDocument(\"model\", null); // create an empty document of type model\nfrom.setString(\"$.phones[0].phone_type\", \"Office\");\nfrom.setString(\"$.phones[0].number\", \"888888\");\nfrom.setString(\"$.phones[0].country\", \"USA\");\nto.merge(from, null);\nString s = to.getPrettyPrintJson();\n```\nA couple of points to note here:\n1. The second parameter of `merge` is a variable string argument and can be used to specify paths\nwhich need to be deleted in the to document before the merge is carried out\n2. If array elements are being merged, JDocs expects the key field to be specified. This is required\nso that JDocs can look up the array element in the to document to merge the from contents into. If no\nsuch array element is found in the to document, a new array element with the specified key is created \n\nJDocs will throw an exception in case of mismatches encountered during merge, for example,\nthe data type of the \"to\" path may not match with that of the \"from\" path\nor the path types may not match i.e. one path may be an array while the other may be an\nobject or a leaf node. In case any such case is encountered, an exception is thrown and\nthe operation aborted.\n\n**CAUTION** -\u003e In case of an exception encountered, whatever merges have been carried out into the from document will\nremain so and the document will not be reverted back. Ideally if both are typed documents, this should not occur. Even\nthen if the application wants the document to be reverted, it should first create a copy of the to document, try the\nmerge into that first and then if it succeeds, carry it out in the first document.\n\nThis feature of merge is used extensively in scenarios where the main document is given to clients as a read only\ndocument and along with it, an empty document (called a fragment) is provided. Whatever the client has to update into\nthe main, it writes into the fragement and returns the fragment to the caller. The caller then merges the fragment into\nthe main document. This gives programs the ability to record incremental updates to a document and later build mechanism\nto play them back to see how the updates occurred.\n\n---\n\n##### Using @here in model documents\n\nConsider a scenario where there is a loan processing application document and its associated model. Assume that\na service is called to the contents of the application document have to be passed in one of the \nJSON fields of the service request. In such a scenario, when a model document for the service request is created,\nthe contents of the model document of the application document have to be embedded in it. Imagine if we\nhad many more such cases and kept embedding application document everywhere as required. This\nwould lead to unnecessary code bloat and making any change to the application model document\nwould require changes at all places where the application model document was embedded. To avoid\nthis, the @here feature is used.\n\nConsider the following application model document:\n\n```json\n{\n  \"first_name\": \"{\\\"type\\\":\\\"string\\\"}\",\n  \"last_name\": \"{\\\"type\\\":\\\"string\\\"}\",\n  \"phones\": [\n    {\n      \"jdocs_arr_pk\": \"{\\\"field\\\":\\\"phone_type\\\"}\",\n      \"phone_type\": \"{\\\"type\\\":\\\"string\\\"}\",\n      \"number\": \"{\\\"type\\\":\\\"string\\\"}\",\n      \"country\": \"{\\\"type\\\":\\\"string\\\"}\"\n    }\n  ]\n}\n```\n\nNow consider that there is a `storeApplication` REST service which persists the contents of the document\nto a database. This service has it own request JSON model document and the contents of the application\nare present in a field. Without the use of @here feature, the model document for tthe service request\nwould look like: \n\n```json\n{\n  \"request_id\": \"{\\\"type\\\":\\\"string\\\"}\",\n  \"application\": {\n    \"first_name\": \"{\\\"type\\\":\\\"string\\\"}\",\n    \"last_name\": \"{\\\"type\\\":\\\"string\\\"}\",\n    \"phones\": [\n      {\n        \"jdocs_arr_pk\": \"{\\\"field\\\":\\\"phone_type\\\"}\",\n        \"phone_type\": \"{\\\"type\\\":\\\"string\\\"}\",\n        \"number\": \"{\\\"type\\\":\\\"string\\\"}\",\n        \"country\": \"{\\\"type\\\":\\\"string\\\"}\"\n      }\n    ]\n  }\n}\n```\n\nNote that the contents of the first json are embedded in the path \"$.application\".\n\nUsing @here, the model document for the service request can be written as:\n\n```json\n{\n  \"request_id\": \"{\\\"type\\\":\\\"string\\\"}\",\n  \"application\": {\n     \"@here\": \"/common/docs/models/application.json\"\n  }\n}\n```\n\nThe @here specifies a file path in the resources folder from where the file contents are read and inserted into\nthe main model document. This eliminates duplication and allows for making the change only at one place.\n\n---\n\n##### Other features\n\n**Searching for an array element**\n\nThe below snippet will search the document for an array element in the phones\narray where the field type has the value home. Note that in order to use this API, the final element\nneeds to be an array in which a selection criteria is specified. Also note that the selection criteria\ncan refer to any field in the element and the index of the match of the first occurrence will be returned. In\ncase it is required to read further, an iteration as described previously is recommended. \n\n```java\nDocument d = new JDocument(s); // s contains a valid JSON string\nint i = 0;\nint index = d.getArrayIndex(\"$.applicants[%].phones[type=home]\", i + \"\");\n```\n\n**Working with array values**\n\nJSON notation also supports array values as below:\n\n```json\n{\n  \"valid_codes\": [\n    \"V1\",\n    \"V2\",\n    \"V3\"\n  ]\n}\n```\n\nJDocs provides a different set of read and write methods to work with such construct. These methods\nare similar to getXXX and setXXX methods and are listed below:\n\n```java\n  Boolean getArrayValueBoolean(String path, String... vargs);\n  Integer getArrayValueInteger(String path, String... vargs);\n  String getArrayValueString(String path, String... vargs);\n  Long getArrayValueLong(String path, String... vargs);\n  BigDecimal getArrayValueBigDecimal(String path, String... vargs);\n  \n  void setArrayValueBoolean(String path, boolean value, String... vargs);\n  void setArrayValueInteger(String path, int value, String... vargs);\n  void setArrayValueLong(String path, long value, String... vargs);\n  void setArrayValueBigDecimal(String path, BigDecimal value, String... vargs);\n  void setArrayValueString(String path, String value, String... vargs);\n```\n\nThe model document for the above would be defined as:\n\n```json\n{\n  \"valid_codes\": [\n    \"{\\\"type\\\":\\\"string\\\"}\"\n  ]\n}\n```\n\n**Flattening a JSON document**\n\nConsider the following JSON snippet:\n\n```json\n{\n  \"id\": \"id_1\",\n  \"family\": {\n    \"members\": [\n      {\n        \"sex\": \"male\",\n        \"first_name\": \"Deepak\",\n        \"last_name\": \"Arora\",\n        \"number_of_dependents\": 3\n      }\n    ]\n  }\n}\n```\n\nThe flatten API of JDocs gives us a list of all the paths present in the document with or without values.\nThe following gets the list of paths without values: \n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing above snippet\nList\u003cString\u003e paths = d.flatten();\npaths.stream.forEach(s -\u003e System.out.println(s));\n```\n\nThe above code will print the following:\n\n```text\n$.id\n$.family.members[0].sex\n$.family.members[0].first_name\n$.family.members[0].last_name\n$.family.members[0].number_of_dependents\n```\n\nIn case we wanted to get the flattened paths and also the values, we would use the `flattenWithValues` API as below:\n\n```java\nDocument d = new JDocument(json); // assuming json is a string containing above snippet\nList\u003cPathValue\u003e paths = d.flatten();\npaths.stream.forEach(pv -\u003e System.out.println(pv.getPath() + \", \" + pv.getValue() + \", \" + pv.getDataType()));\n```\n\nThe above code will print the following:\n\n```text\n$.id, id_1, string\n$.family.members[0].sex, male, string\n$.family.members[0].first_name, Deepak, string\n$.family.members[0].last_name, Arora, string\n$.family.members[0].number_of_dependents, 3, integer\n```\n\n**Comparing two JSON documents**\n\nWe can use the `getDifferences` API to compare one JSON document with another. Consider the following\nJSON documents:\n\nJSON snippet 1:\n\n```json\n{\n  \"id\": \"id_1\",\n  \"family\": {\n    \"members\": [\n      {\n        \"first_name\": \"Deepak\"\n      }\n    ]\n  },\n  \"cars\": [\n    {\n      \"make\": \"Honda\",\n      \"model\": null\n    }\n  ],\n  \"vendors\": [\n    \"v1\",\n    \"v2\"\n  ]\n}\n```\n\nJSON snippet 2:\n```json\n{\n  \"id\": \"id_2\",\n  \"family\": {\n    \"members\": [\n      {\n        \"first_name\": \"Deepak\"\n      },\n      {\n        \"first_name\": \"Nitika\"\n      }\n    ]\n  },\n  \"vendors\": [\n    \"v1\",\n    \"v3\"\n  ]\n}\n```\n\nThe comparison can be done as below:\n\n```java\nDocument ld = new JDocument(jsonLeft); // assuming jsonLeft is a string containing above snippet 1\nDocument rd = new JDocument(jsonRight); // assuming jsonRight is a string containing above snippet 2\nList\u003cDiffInfo\u003e diList = ld.getDifferences(rd, true);\nString s = \"\";\nfor (DiffInfo di : diList) {\n  String lpath = (di.getLeft() == null) ? \"null\" : di.getLeft().getPath();\n  String rpath = (di.getRight() == null) ? \"null\" : di.getRight().getPath();\n  s = s + di.getDiffResult() + \", \" + lpath + \", \" + rpath + \"\\n\";\n}\nSystem.out.println(s);\n```\n\nThe above would print:\n\n```text\nDIFFERENT, $.id, $.id\nONLY_IN_LEFT, $.cars[0].make, null\nDIFFERENT, $.vendors[1], $.vendors[1]\nONLY_IN_RIGHT, null, $.family.members[1].first_name\n````\n\nOf course, if we wanted to also get the values, we could always use `getValue` method of `PathValue`\nobject returned from the call `di.getLeft()` or `di.getRight`. We could also use the method\n`getDataType` in case we wanted to get the data type of the value.\n\nPlease note the following regarding comparing JSON documents:\n1. JDocs does a logical comparison of the documents. When fields with null values are encountered, they\nare treated as being equivalent to the field not being present in the document. In the above example,\nin the left document, `$.cars[0].model` has null value while this path is not present in the right document.\nJDocs comparison assumes that these are equivalent. In other words a JSON path leaf node having a null values\nis assumed to be the same as the path not existing at all.\n1. If any one of the documents being compared is a typed document, the data type of the path will be determined\nfrom the model document\n \n---\n\n##### What is unique about JDocs?\n\n1. Ability to write data to JSON documents using JSON paths for fields AND complex objects AND arrays\n2. A very simple and easy to use feature of binding the structure of a JSON document to a model\n   which can then be used to enforce the integrity of the structure during read and writes\n3. Ability to define regex constraints on fields in the model which are used to validate the field values at the time of\n   writing\n4. Ability to merge documents into one another including merging complex objects and arrays nested any levels deep\n5. Ability to reuse and embed models\n6. Ability to copy content from one document to another using JSON paths\n7. And most of all freedom from model classes and code bloat\n\n---\n\n#### JDocs - The Core API\n\nThe core API can be referred to in the file `Document.java`.\n\n---\n\n##### What next?\n\nGo through the unit test cases in the source code. Unit test cases are available in the location `src/test`\n\nProvide us feedback. We would love to hear from you.\n\n---\n\n##### Author:\nDeepak Arora, GitHub: @deepakarora3, Twitter: @DeepakAroraHi\n\n---\n\n## Contributing\n\nWe welcome Your interest in the American Express Open Source Community on Github. Any Contributor to\nany Open Source Project managed by the American Express Open Source Community must accept and sign\nan Agreement indicating agreement to the terms below. Except for the rights granted in this \nAgreement to American Express and to recipients of software distributed by American Express, You\nreserve all right, title, and interest, if any, in and to Your Contributions. Please\n[fill out the Agreement](https://cla-assistant.io/americanexpress/unify-jdocs).\n\n## License\n\nAny contributions made under this project will be governed by the\n[Apache License 2.0](./LICENSE.txt).\n\n## Code of Conduct\n\nThis project adheres to the [American Express Community Guidelines](./CODE_OF_CONDUCT.md). By\nparticipating, you are expected to honor these guidelines.\n","funding_links":[],"categories":["Java"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famericanexpress%2Funify-jdocs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famericanexpress%2Funify-jdocs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famericanexpress%2Funify-jdocs/lists"}