{"id":21131169,"url":"https://github.com/wnameless/jpa-type-flattenedjson","last_synced_at":"2026-05-17T17:33:29.531Z","repository":{"id":50112845,"uuid":"169970572","full_name":"wnameless/jpa-type-flattenedjson","owner":"wnameless","description":"Simulate a new datatype FlattenedJson in database based on the AttributeConverter feature of JPA 2.1 with Hibernate, QueryDsl, Jackson JSON and JsonFlattener.","archived":false,"fork":false,"pushed_at":"2021-06-04T01:47:06.000Z","size":67,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-10T14:39:19.528Z","etag":null,"topics":["flattened-json","hibernate","jackson-databind","java","jpa","json","querydsl"],"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/wnameless.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-10T11:24:40.000Z","updated_at":"2024-03-07T08:28:24.000Z","dependencies_parsed_at":"2022-08-30T09:11:01.290Z","dependency_job_id":null,"html_url":"https://github.com/wnameless/jpa-type-flattenedjson","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/wnameless/jpa-type-flattenedjson","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wnameless%2Fjpa-type-flattenedjson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wnameless%2Fjpa-type-flattenedjson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wnameless%2Fjpa-type-flattenedjson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wnameless%2Fjpa-type-flattenedjson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wnameless","download_url":"https://codeload.github.com/wnameless/jpa-type-flattenedjson/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wnameless%2Fjpa-type-flattenedjson/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33147544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T09:28:26.183Z","status":"ssl_error","status_checked_at":"2026-05-17T09:27:52.702Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["flattened-json","hibernate","jackson-databind","java","jpa","json","querydsl"],"created_at":"2024-11-20T05:50:10.892Z","updated_at":"2026-05-17T17:33:29.514Z","avatar_url":"https://github.com/wnameless.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.wnameless/jpa-type-flattenedjson/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.wnameless/jpa-type-flattenedjson)\n\njpa-type-flattenedjson\n=============\nSimulate a new datatype *FlattenedJson* in database based on the AttributeConverter feature of JPA 2.1 with Hibernate, QueryDsl, Jackson JSON and JsonFlattener.\n\n# Goal\n- Make all kinds of relational databases to support JSON data format with as little effort as possible. \u003cbr\u003e\n- Allow user to search arbitrary JSON data through QueryDsl JPA without using database specified functions(Ex: JSON_CONTAINS).\n\n## Maven Repository\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003ecom.github.wnameless\u003c/groupId\u003e\n\t\u003cartifactId\u003ejpa-type-flattenedjson\u003c/artifactId\u003e\n\t\u003cversion\u003e0.2.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Concept Brief\nNormally JSON format can not be queried directly with SQL, it's required the database to provide special functions to search JSON data. For example, the JSON_CONTAINS function in MySQL database. \u003cbr\u003e\n\nHowever, all those special functions are not well supported by all RDBMS and it tends to break the SQL convention somehow. \u003cbr\u003e\n\nAttributeConverter was introduced in JPA 2.1. It allows any field of an entity class to be converted to JSON string which can also be stored as Varchar in all databases. \u003cbr\u003e\n\nApplying [JsonFlattener](https://github.com/wnameless/json-flattener) on stored JSON strings makes us possible to search a flattened JSON data by regular SQL LIKE or REGEXP related function without losing performance.\n\nUsually to search a JSON data with regexp is a bad idea because JSON is not regular. Since it allows arbitrary embedding of nested data, it is almost near context-free. But a flattened JSON is much more regular, so using regexp on *FlattenedJson* is way more efficient and easier.\n\n## Howto \nTurn arbitrary objects into flattened JSON strings and store them into database as Character datatype.\n```java\n@Entity\npublic class TestModel {\n\n  @GeneratedValue\n  @Id\n  Long id;\n\n  @Column(length = 4000)\n  @Convert(converter = JsonNodeConverter.class)\n  JsonNode props; // JsonNode is from jackson-databind library\n\n  @Column(length = 4000)\n  // Implemented by extending the abstract ToFlattenedJsonConverter class\n  @Convert(converter = TestModelAttrConverter.class)\n  TestModelAttr testAttr;\n\n}\n\npublic class TestModelAttr {\n\n  private List\u003cInteger\u003e numbers = new ArrayList\u003c\u003e();\n\n  private List\u003cMap\u003cString, String\u003e\u003e words = new ArrayList\u003c\u003e();\n\n  // Getters and Setters...\n}\n```\n```java\n@Autowired\nTestModelRepository testModelRepo; // Spring Data\n\nTestModel testModel = new TestModel();\ntestModel.setProps(FlattenedJsonTypeConfigurer.INSTANCE.getObjectMapperFactory()\n  .get().readTree(\"{ \\\"abc\\\": { \\\"CBA\\\": 123 } }\"));\n\nTestModelAttr tma = new TestModelAttr();\n\ntma.getNumbers().add(3);\ntma.getNumbers().add(2);\ntma.getNumbers().add(1);\n\ntma.getWords().add(new HashMap() {{ put(\"abc\", \"XYZ\"); }});\ntma.getWords().add(new HashMap() {{ put(\"DEF\", \"uvw\"); }});\nmodel.setTestAttr(tma);\n\ntestModelRepo.save(model);\n\n// The actual data stored in database:\n// | id | props           | test_attr                                                                                |\n// |----|-----------------|------------------------------------------------------------------------------------------|\n// | 1  | {\"abc.CBA\":123} | {\"numbers[0]\":3,\"numbers[1]\":2,\"numbers[2]\":1,\"words[0].abc\":\"XYZ\",\"words[1].DEF\":\"uvw\"} |\n```\n\nQuery the stored data by [QueryDsl](https://github.com/querydsl/querydsl) with SQL LIKE and REGEXP_LIKE functions supported. \u003cbr\u003e\nQTestModel can be generated by [QueryDsl APT](http://www.querydsl.com/static/querydsl/latest/reference/html/ch02.html).\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nlong count = query\n  .from(qTestModel)\n  .where(QueryDslHelper.flattenedJsonLike(qTestModel.testAttr, \"numbers[0]\", \"3\"))\n  .fetchCount();\n  \ncount = query\n  .from(qTestModel)\n  .where(QueryDslHelper.flattenedJsonRegexpLike(qTestModel.testAttr, \"numbers[0]\", \"\\\\d+\"))\n  .fetchCount();\n```\n\n## Quick Start\nAnnotate any field in JPA Entity class with @Convert and a converter class which extends ToFlattenedJsonConverter abstract class.\n```java\n@Convert(converter = TestModelAttrConverter.class)\n```\n```java\npublic class TestModelAttrConverter\n    extends ToFlattenedJsonConverter\u003cTestModelAttr\u003e {\n\n  @Override\n  protected TypeReference\u003cTestModelAttr\u003e getAttributeTypeReference() {\n    return new TypeReference\u003cTestModelAttr\u003e() {};\n  }\n\n}\n```\n\n## Features\nBecause REGEXP of databases is supported in different ways, it is required a little configuration to enable this feature. \u003cbr\u003e\nSo far, Hibernate is the only ORM supported. \u003cbr\u003e\n\nThe following table shows all tested databases: \u003cbr\u003e\n\n| Database   |  REGEXP_LIKE  |  REGEXP_MATCHES | SUBSTRING\n|------------|---------------|-----------------|----------|\n| H2         |  \u0026#9745;      |  \u0026#9744;        |  \u0026#9744; |\n| HSQLDB     |  \u0026#9744;      |  \u0026#9745;        |  \u0026#9744; |\n| MySQL      |  \u0026#9745;      |  \u0026#9744;        |  \u0026#9744; |\n| PostgreSQL |  \u0026#9744;      |  \u0026#9744;        |  \u0026#9745; |\n\n### Configuration (Since v0.2.0, REGEXP_MATCHES and SUBSTRING are also supported.)\nPick either of configurations listed below which fits your database: \u003cbr\u003e\n\nSpring application.properties\n```javascript\n// Add REGEX_LIKE function support to Hibernate\nhibernate.metadata_builder_contributor=com.github.wnameless.jpa.type.flattenedjson.hibernate.RegexpLikeSqlFunctionContributor\n```\n```javascript\n// Add REGEX_MATCHES function support to Hibernate\nhibernate.metadata_builder_contributor=com.github.wnameless.jpa.type.flattenedjson.hibernate.RegexpMatchesSqlFunctionContributor\n```\n```javascript\n// Add SUBSTRING function support to Hibernate\nhibernate.metadata_builder_contributor=com.github.wnameless.jpa.type.flattenedjson.hibernate.SubstringSqlFunctionContributor\n```\nJava persistence.xml\n```xml\n\u003cproperty\u003e\n    name=\"hibernate.metadata_builder_contributor\" \n    value=\"com.github.wnameless.jpa.type.flattenedjson.hibernate.RegexpLikeSqlFunctionContributor\"\n\u003c/property\u003e\n```\n```xml\n\u003cproperty\u003e\n    name=\"hibernate.metadata_builder_contributor\" \n    value=\"com.github.wnameless.jpa.type.flattenedjson.hibernate.RegexpMatchesSqlFunctionContributor\"\n\u003c/property\u003e\n```\n```xml\n\u003cproperty\u003e\n    name=\"hibernate.metadata_builder_contributor\" \n    value=\"com.github.wnameless.jpa.type.flattenedjson.hibernate.SubstringSqlFunctionContributor\"\n\u003c/property\u003e\n```\n\n### QueryDslHelper\n#### LIKE\nThis query pattern need to be provide completely.\n```java\n@Autowired\nTestModelRepository testModelRepo; // Spring Data\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.like(qTestModel.testAttr, \"'%\\\"numbers[0]\\\":3,%'\");\ntestModelRepo.count(exp); \n```\nIgnore case\n```java\nQueryDslHelper.like(qTestModel.testAttr, \"'%\\\"NUMBERS[0]\\\":3,%'\", true);\n```\n\n#### FlattenedJson LIKE\nJust simply provide the JSON key and value, then the LIKE query pattern is created automatically.\n```java\n@Autowired\nTestModelRepository testModelRepo; // Spring Data\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.flattenedJsonlike(qTestModel.testAttr, \"numbers[0]\", \"3\");\ntestModelRepo.count(exp); \n```\nIgnore case\n```java\nQueryDslHelper.flattenedJsonlike(qTestModel.testAttr, \"NUMBERS[0]\", \"3\", true);\n```\n\n#### REGEXP_LIKE\nThis query pattern need to be provide completely.\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.regexpLike(qTestModel.testAttr,\n    QueryDslHelper.REGEXP_PAIR_PREFIX    // \"[{,]\" + \"\\\"\"\n    + QueryDslHelper.quoteRegExSpecialChars(\"numbers[0]\")\n    + QueryDslHelper.REGEXP_PAIR_INFIX   // \"\\\":\"\n    + \"\\\\d+\"\n    + QueryDslHelper.REGEXP_PAIR_SUFFIX); // \"[,}]\"\n\nquery.from(qTestModel).where(exp).fetchCount();\n```\n\n#### FlattenedJson REGEXP_LIKE\nJust simply provide the JSON key and REGEXP of value, then the query pattern is created automatically.\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.flattenedJsonRegexpLike(qTestModel.testAttr, \"numbers[0]\", \"\\\\d+\");\nquery.from(qTestModel).where(exp).fetchCount();\n```\nBy default, the key is quoted. This can be disable by doing this:\n```java\nQueryDslHelper.flattenedJsonRegexpLike(qTestModel.testAttr, \"numbers\\\\[0\\\\]\", \"\\\\d+\", false);\n```\n\n#### REGEXP_MATCHES\nThis query pattern need to be provide completely.\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.regexpMatches(qTestModel.testAttr,\n    QueryDslHelper.REGEXP_PAIR_PREFIX    // \"[{,]\" + \"\\\"\"\n    + QueryDslHelper.quoteRegExSpecialChars(\"numbers[0]\")\n    + QueryDslHelper.REGEXP_PAIR_INFIX   // \"\\\":\"\n    + \"\\\\d+\"\n    + QueryDslHelper.REGEXP_PAIR_SUFFIX); // \"[,}]\"\n\nquery.from(qTestModel).where(exp).fetchCount();\n```\n\n#### FlattenedJson REGEXP_MATCHES\nJust simply provide the JSON key and REGEXP of value, then the query pattern is created automatically.\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.flattenedJsonRegexpMatches(qTestModel.testAttr, \"numbers[0]\", \"\\\\d+\");\nquery.from(qTestModel).where(exp).fetchCount();\n```\nBy default, the key is quoted. This can be disable by doing this:\n```java\nQueryDslHelper.flattenedJsonRegexpMatches(qTestModel.testAttr, \"numbers\\\\[0\\\\]\", \"\\\\d+\", false);\n```\n\n#### SUBSTRING_MATCHES\nBecause we only care if the SUBSTRING MATCHES regexp pattern, not actually want to aquire the substring itself. The function is named as **#substringMatches** intead of **#substring** to avoid misunderstanding.\n\nThis query pattern need to be provide completely.\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.substringMatches(qTestModel.testAttr,\n    QueryDslHelper.REGEXP_PAIR_PREFIX    // \"[{,]\" + \"\\\"\"\n    + QueryDslHelper.quoteRegExSpecialChars(\"numbers[0]\")\n    + QueryDslHelper.REGEXP_PAIR_INFIX   // \"\\\":\"\n    + \"\\\\d+\"\n    + QueryDslHelper.REGEXP_PAIR_SUFFIX); // \"[,}]\"\n\nquery.from(qTestModel).where(exp).fetchCount();\n```\n\n#### FlattenedJson SUBSTRING_MATCHES\nJust simply provide the JSON key and REGEXP of value, then the query pattern is created automatically.\n```java\nJPAQuery\u003cTestModel\u003e query = new JPAQuery\u003cTestModel\u003e(entityManager);\nQTestModel qTestModel = QTestModel.testModel;\n\nBooleanExpression exp = QueryDslHelper.flattenedJsonSubstringMatches(qTestModel.testAttr, \"numbers[0]\", \"\\\\d+\");\nquery.from(qTestModel).where(exp).fetchCount();\n```\nBy default, the key is quoted. This can be disable by doing this:\n```java\nQueryDslHelper.flattenedJsonSubstringMatches(qTestModel.testAttr, \"numbers\\\\[0\\\\]\", \"\\\\d+\", false);\n```\n\n### ToFlattenedJsonConverter\nA base class to create a new JPA Converter of arbitrary type for FlattenedJson.\n```java\n@Converter\npublic class AnyTypeConverter extends ToFlattenedJsonConverter\u003cAnyType\u003e {\n\n  @Override\n  protected TypeReference\u003cAnyType\u003e getAttributeTypeReference() {\n    return new TypeReference\u003cAnyType\u003e() {};\n  }\n\n}\n```\nJsonNodeConverter is already provided in library.\n\n### FlattenedJsonTypeConfigurer\nFlattenedJsonTypeConfigurer is an enum with a single vlaue INSTANCE which also implies it's a singleton.\n```java\nFlattenedJsonTypeConfigurer.INSTANCE\n```\n\nFlattenedJsonType is powered by [JsonFlattener](https://github.com/wnameless/json-flattener).\n```java\nFlattenedJsonTypeConfigurer.INSTANCE.getJsonFlattenerCustomizer();\nFlattenedJsonTypeConfigurer.INSTANCE.setJsonFlattenerCustomizer(Function\u003cJsonFlattener, JsonFlattener\u003e jsonFlattenerCustomizer);\nFlattenedJsonTypeConfigurer.INSTANCE.getJsonUnflattenerCustomizer();\nFlattenedJsonTypeConfigurer.INSTANCE.setJsonUnflattenerCustomizer(Function\u003cJsonUnflattener, JsonUnflattener\u003e jsonUnflattenerCustomizer);\n```\nFlattenedJsonType is powered by [jackson-databind](https://github.com/FasterXML/jackson-databind) as well.\n```java\nFlattenedJsonTypeConfigurer.INSTANCE.getObjectMapperFactory();\nFlattenedJsonTypeConfigurer.INSTANCE.setObjectMapperFactory(Supplier\u003cObjectMapper\u003e objectMapperFactory);\n```\n\nAny modification in FlattenedJsonTypeConfigurer will take effects on the entire library.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwnameless%2Fjpa-type-flattenedjson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwnameless%2Fjpa-type-flattenedjson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwnameless%2Fjpa-type-flattenedjson/lists"}