{"id":19004885,"url":"https://github.com/dhis2/json-tree","last_synced_at":"2025-04-22T18:49:44.670Z","repository":{"id":37921183,"uuid":"429373405","full_name":"dhis2/json-tree","owner":"dhis2","description":"A lazily parsed JSON object tree library","archived":false,"fork":false,"pushed_at":"2025-02-27T15:35:45.000Z","size":437,"stargazers_count":5,"open_issues_count":7,"forks_count":2,"subscribers_count":23,"default_branch":"main","last_synced_at":"2025-04-17T10:24:40.869Z","etag":null,"topics":["json","json-schema","json-schema-validator","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dhis2.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-11-18T09:42:17.000Z","updated_at":"2025-02-27T15:35:49.000Z","dependencies_parsed_at":"2024-06-27T15:57:50.982Z","dependency_job_id":"f07d5468-94e5-45d7-8ff8-de4316358a7d","html_url":"https://github.com/dhis2/json-tree","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fjson-tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fjson-tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fjson-tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fjson-tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dhis2","download_url":"https://codeload.github.com/dhis2/json-tree/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250302568,"owners_count":21408407,"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":["json","json-schema","json-schema-validator","testing-tools"],"created_at":"2024-11-08T18:25:05.468Z","updated_at":"2025-04-22T18:49:44.648Z","avatar_url":"https://github.com/dhis2.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# json-tree\n\n[![main](https://github.com/dhis2/json-tree/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/dhis2/json-tree/actions/workflows/main.yml)\n\nA lazily parsed JSON virtual tree library.\n\nVirtually here means it represents the \"wanted\" or \"expected\" tree structure.\nOn leaf value access it is checked against the actual tree structure underneath.\nAs a result tree navigation is painless and checks only have to be made for \nand when values are resolved (extracted) from the actual tree.\n\nLazily parsed means only the parts of the underlying tree that are used during\nnavigation will be parsed into nodes which remember offsets (\"pointers\") into \nthe original JSON input.\nUntouched parts of a JSON tree are skipped over until they are used.\nThis makes for a fast and memory efficient implementation that is convenient \nto query and extend with user types that can handle inputs in MB range without\nproblem. \n\n## Java to JSON Tree\nCreating a (virtual) tree from Java types is handled by the `Json` API.\n\n```java\nJsonString s  = Json.of(\"hello\"); // note: not quoted\nJsonBoolean b = Json.of(true);\nJsonInteger i = Json.of(42);\nJsonNumber d  = Json.of(42.01d);\nJsonArray a   = Json.array(Json::of, List.of(1,2,3));\nJsonObject o  = Json.object(Json::of, Map.of(\"a\", 4, \"b\", 2));\n```\n\nArrays and objects can also be composed in various ways using a builder API.\n```java\nJsonArray a = Json.array(arr -\u003e arr.addNumber(1).addString(\"yes\"));\na.toJson();   // [1,\"yes\"]\n\nJsonObject o = Json.object(obj -\u003e obj.addNumber(\"x\", 1).addNumber(\"y\", 5));\no.toJson();   // {\"x\":1,\"y\":5}\n\nJsonObject o2 = Json.object(obj -\u003e obj.addArray(\"points\", \n        arr -\u003e arr.addNumbers(IntStream.range(0,10))));\no2.toJson();  // {\"points\":[1,2,3,4,5,6,7,8,9]}\n```\n\n## JSON to JSON Tree\nCreating a (virtual) tree from JSON is handled by the `JsonMixed` and \n`JsonValue` API.\n\n`JsonMixed` is the union type (top type) of all basic JSON types, \nwhile `JsonValue` is the base type (bottom type) of all basic JSON types.\n\nThe basic types are `JsonBoolean`, `JsonNumber`, `JsonInteger`, `JsonString`, \n`JsonArray` and `JsonObject`. \nAn instance of these represents a virtual tree node. \nTheir values are then accessed using the appropriate access method on the node.\n\nRepresenting JSON `null` as type is not necessary since in a virtual tree each\nnode can be of the type that is wanted, not the type actually present.\nHence, the actual value for any type can be undefined or defined `null`.\n\n```java\nJsonString s = JsonMixed.of(\"null\");\ns.exists();      // true, it is a NULL node in the actual tree\ns.isUndefined(); // true\ns.isNull();      // true\ns.isString();    // false\ns.type();        // JsonNodeType.NULL\ns.string();      // throws exception\n```\nWhereas nodes can actually not exist. For example:\n```java\nJsonString s = JsonMixed.of(\"{}\").getString(\"x\"); // the member \"x\" as string\ns.exists();      // false\ns.isUndefined(); // true\ns.type();        // null (Java null)        \ns.string();      // throws exception\n```\n\nWhen creating a virtual tree using `JsonMixed.of` or `JsonValue.of` the\ninput must be valid JSON. This means a string must be quoted.\n\n```java\nJsonString s = JsonMixed.of(\"\\\"null\\\"\");\ns.isString();    // true\ns.string();      // \"null\" (as Java string)\ns.type();        // JsonNodeType.STRING\n```\n\nEqually, to create a virtual tree for a complex JSON input:\n```java\nString json = \"\"\"\n    {   \n        \"key\": \"points\", \n        \"value\": [1,2,3] \n    }\n    \"\"\";\nJsonObject o = JsonMixed.of(json);\n```\n\n## User Types as JSON Tree\nThe pre-defined basic types of the virtual tree can easily be extended and mixed\nwith user defined ones. Like the basic types these are defined using `interface`s.\nProperties are represented as `default` methods to build a typical \"object model\".\n\n```java\ninterface JsonAddress extends JsonObject {\n    default String street() {\n        return getString(\"street\").string();\n    }\n    default Integer no() {\n        return getNumber(\"no\").integer();\n    }\n    default JsonString city() {\n        return getString(\"city\");\n    }\n}\n```\nWhile being convenient to define the user has full control over conversions and \nweather or not properties are used on the value level or the node level (city).\nThis also allows for logic in the property methods (e.g. for default or parsing).\n\n```java\nJsonObject obj = JsonMixed.of(\"\"\"\n    {\n        \"street\": \"Elm\", \n        \"no\": 11, \n        \"city\": \"Oslo\"\n    }\"\"\");\nJsonAddress a = obj.as(JsonAddress.class); // \"viewing\" the object as address\na.street();        // = \"Elm\" (Java String)\na.no();            // = 11 (Java Integer)\na.city().exits();  // as city() is merely the node we can check for existence\na.city().string(); // = \"Oslo\" (Java String)\n```\n\nEntire domain models can easily be created using this approach.\n\n```java\ninterface JsonUser extends JsonObject {\n    default JsonAddress address() {\n        return get( \"address\", JsonAddress.class );\n    }\n    default JsonList\u003cJsonAddress\u003e alternativeAddresses() {\n        return getList( \"alternativeAddresses\", JsonAddress.class );\n    }\n}\n```\n\n## Validation of a JSON Tree\nSince a virtual tree describes the \"wanted\" structure a common use case is to\ncheck the actual input against this target. For that objects can be validated.\n\n```java\nJsonObject obj = JsonMixed.of(\"\"\"\n    {\n        \"city\": \"Oslo\", \n        \"no\": 42\n    }\"\"\");\nobj.validate(JsonAddress.class); // node types match, no exception\n```\n\nThe basic types all include validation for the JSON node type. This means a\nstring property cannot have an actual value that is a JSON number and so on.\nTo extend the requirements additional validations can be added using \nannotations.\n\n```java\ninterface JsonAddress extends JsonObject {\n    @Required\n    @Validation( maxLength = 50 )\n    default String street() {\n        return getString( \"street\" ).string();\n    }\n    @Validation( required = YesNo.YES, minimum = 1)\n    default Integer no() {\n        return getNumber( \"no\" ).integer();\n    }\n    @Validation( maxLength = 50 )\n    default JsonString city() {\n        return getString( \"city\" );\n    }\n}\n```\nWith the improved `JsonAddress` declaration a JSON input that lacks `street`\nwould now result in an error and throw an exception when calling \n`obj.validate(JsonAdress.class)`.\n\nValidation is either given by annotating with `@Validation` or using meta\nannotation like `@Required`. The provided validation follow the JSON schema\nvalidation specification.\n\nUsers can simply create their own meta annotations by annotating the annotation \ntype with `@Validation`, for example:\n\n```java\n@Target( ElementType.METHOD )\n@Retention( RetentionPolicy.RUNTIME )\n@Validation( mininum = 1 )\npublic @interface Positive {}\n```\n\nTo check conformity with a given schema type `JsonObject::isA` can be used. \nThe `JsonObject::asA` is same as `JsonValue::as` with a validation check before\nthe \"cast\".\n```java\nJsonObject obj = JsonMixed.of(\"\"\"\n    {\n        \"city\": \"Oslo\", \n        \"no\": 0, \n        \"street\": \"Elm\"\n    }\"\"\");\nobj.isA(JsonAddress.class);                 // false, no \u003c 1\nJsonAddress a = obj.asA(JsonAddress.class); // throws exception, no \u003c 1\n```\nBoth `isA` and `asA` can also be limited to a given set of validation `Rule`\ntypes.\n\n\n## Comparing JSON values (diff)\nWhen asserting that the JSON returned by a service contains\nthe expected information a usual challenge is that the\ncomparison cannot rely on a specifics such as\n\n* the whitespace (formatting)\n* the order of properties in objects\n* the order of elements in an array that is a \"set\"\n* the presence of additional object properties\n* the exact way numbers are formatted\n\nTo make such comparisons easy the `diff` method can be used\nto find the differences between two `JsonValue` instances.\nWhen computing the difference the comparison can be configured\nusing the `JsonDiff.Mode`.\n\n```java\nJsonValue expected = JsonValue.of( \"[1,2,3]\" );\nJsonValue actual = JsonValue.of( \"[1,3,2]\" );\n\nJsonDiff diff1 = expected.diff( actual ); // is different\nJsonDiff diff2 = expected.diff( actual, JsonDiff.Mode.LENIENT); // same\n```\n\nTo adjust individual properties of `JsonObject` subtypes the `default` \nmethods of its properties can be annotated with `@AnyOrder` and \n`@AnyAdditional` to opt into a lenient handling.\n\n```java\ninterface MyObject extends JsonObject {\n\n    @JsonDiff.AnyOrder\n    default JsonArray tags() {\n        return getArray( \"tags\" );\n    }\n}\n```\nThe used `Mode` is now overridden for the annotated property:\n```java\nMyObject expected = JsonValue.of( \"\"\"\n    {\"tags\": [\"hello\", \"intro\"]}\"\"\" ).as( MyObject.clas );\nMyObject actual = JsonValue.of( \"\"\"\n    {\"tags\": [\"intro\", \"hello\"]}\"\"\" ).as( MyObject.clas );\n\nJsonDiff diff1 = expected.diff( actual ); // same\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhis2%2Fjson-tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdhis2%2Fjson-tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhis2%2Fjson-tree/lists"}