{"id":20580259,"url":"https://github.com/maif/functional-json","last_synced_at":"2026-04-02T18:43:30.229Z","repository":{"id":38450067,"uuid":"297327445","full_name":"MAIF/functional-json","owner":"MAIF","description":"Parse and write json the functional way","archived":false,"fork":false,"pushed_at":"2021-12-03T14:59:03.000Z","size":124,"stargazers_count":11,"open_issues_count":3,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-28T07:51:18.655Z","etag":null,"topics":["functional-programming","jackson","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/MAIF.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-21T12:05:50.000Z","updated_at":"2024-08-25T13:20:14.000Z","dependencies_parsed_at":"2022-08-19T15:52:44.606Z","dependency_job_id":null,"html_url":"https://github.com/MAIF/functional-json","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MAIF%2Ffunctional-json","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MAIF%2Ffunctional-json/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MAIF%2Ffunctional-json/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MAIF%2Ffunctional-json/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MAIF","download_url":"https://codeload.github.com/MAIF/functional-json/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248946503,"owners_count":21187514,"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":["functional-programming","jackson","json"],"created_at":"2024-11-16T06:22:12.473Z","updated_at":"2026-04-02T18:43:30.185Z","avatar_url":"https://github.com/MAIF.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Functional json  [![ga-badge][]][ga] [![jar-badge][]][jar]\n\n[ga]:               https://github.com/MAIF/functional-json/actions?query=workflow%3ABuild\n[ga-badge]:         https://github.com/MAIF/functional-json/workflows/Build/badge.svg\n[jar]:              https://maven-badges.herokuapp.com/maven-central/fr.maif/functional-json\n[jar-badge]:        https://maven-badges.herokuapp.com/maven-central/fr.maif/functional-json/badge.svg\n\n\nThis library inspired by [playframework scala json](https://github.com/playframework/play-json) lib and [json-lib](https://github.com/mathieuancelin/json-lib) provide helpers to manipulate [Jackson](https://github.com/FasterXML/jackson) json nodes. \nWith this lib you can have a total control on json serialization and deserialization. \nYou can also separate POJO's definition from its serialization, which can be helpful in an hexagonal architecture. \nAnother benefit is to get various ser/des for the same class. \n\nAt the end you will get a better error handling which can be very pleasant in RESTful API to help the client of the API to understand why the validation has failed.\n\n\nTo read and write from json, there is two important interface : \n\n* `JsonRead` to read json \n* `JsonWrite` to write json \n\nThis lib also comes with helpers to build json from scratch easily than with raw jackson.\n\n## Import\n\nJcenter hosts this library.\n\n### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003efr.maif\u003c/groupId\u003e\n    \u003cartifactId\u003efunctional-json\u003c/artifactId\u003e\n    \u003cversion\u003e${VERSION}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Gradle\n```\nimplementation 'fr.maif:functional-json:${VERSION}'\n```\n\n## Creating json \n\nYou can create json using the `$` static function or his alias `$$` in case `$` is already in scope (eg `vavr` pattern matching) to create a json object.\n\n```java\nimport fr.maif.json.Json.*;\nimport fr.maif.json.JsonWrite.*;\n```\n\n```java\nObjectNode myJson = Json.obj(\n        $(\"name\", \"Ragnar Lodbrock\"),\n        $(\"city\", Some(\"Kattegat\")),\n        $(\"weight\", 80),\n        $(\"birthDate\", LocalDate.of(766, 1, 1), $localdate()),\n        $(\"sons\", Json.arr(\n            Json.obj($(\"name\", \"Bjorn\")),\n            Json.obj($(\"name\", \"Ubbe\")),\n            Json.obj($(\"name\", \"Hvitserk\")),\n            Json.obj($(\"name\", \"Sigurd\")),\n            Json.obj($(\"name\", \"Ivar\"))\n        ))\n);\n```\n\n## JsonRead\n\nReading JSON is basic :\n\n```java\n@FunctionalInterface\npublic interface JsonRead\u003cT\u003e {\n\n    JsResult\u003cT\u003e read(JsonNode jsonNode);\n    \n}\n``` \nA `JsResult` can rather be a `JsSuccess` or a `JsError`. The `JsError` will stack all errors, in order to let you a detailed report of what happend.\n\n\nWith a lambda you can define a read this way :\n\n```java\nJsonRead\u003cString\u003e strRead = json -\u003e {\n   if (json.isTextual()) {\n       return JsResult.success(json.asText());\n   } else {\n       return JsResult.error(List.of(JsResult.Error.error(\"string.expected\")));\n   }\n};\n```\n\nThis lib already provides readers for all common types so you probably won't have to write this kind of code. \n\nIf you need to implement a specific reader for a POJO, this will look like this.\n\nThe POJO with a builder and immutable fields. \n\nThe `@FieldNameConstants` is a lombok annotation that will create a sub class with static fields with the name of each fields. \n\n```java\n@FieldNameConstants\n@Builder\n@AllArgsConstructor\npublic static class Viking {\n    public final String firstName;\n    public final String lastName;\n    public final Option\u003cString\u003e city;\n}\n```\n\nAnd the reader is the following : \n\n```java\npublic static JsonRead\u003cViking\u003e reader() {\n    return _string(\"firstName\", Viking.builder()::firstName)\n            .and(_string(\"lastName\"), Viking.VikingBuilder::lastName)\n            .and(_opt(\"city\", _string()), Viking.VikingBuilder::city)\n            .map(Viking.VikingBuilder::build);\n} \n```\n\nFor a better understanding, here is the decomposition of the previous code : \n\n\nThis part reads a string at path \"firstName\"\n```java\nJsonRead\u003cString\u003e stringJsonRead = _string(\"firstName\");\n```\n\nHere is an alternative, that reads a `String` at a path in order to use this `String` for something else.\nThen you'll need to create a builder and apply the string to the `firstName` field :\n```java\nJsonRead\u003cVikingBuilder\u003e vikingBuilderJsonRead = _string(\"firstName\", str -\u003e Viking.builder().firstName(str));\n```\n\nThe same with method reference : \n```java\nJsonRead\u003cVikingBuilder\u003e vikingBuilderJsonRead2 = _string(\"firstName\", Viking.builder()::firstName);\n```\n\nNow we can use the method `and`. \nWith `and` you can read another field and combine with the current builder like this :   \n```java\nJsonRead\u003cVikingBuilder\u003e vikingBuilderJsonReadStep2 = vikingBuilderJsonRead2\n    .and(_string(\"lastName\"), (previousBuilder, str) -\u003e previousBuilder.lastName(str));\n```\n\nThe same with method reference : \n\n```java\nJsonRead\u003cVikingBuilder\u003e vikingBuilderJsonReadStep2 = vikingBuilderJsonRead2\n    .and(_string(\"lastName\"), VikingBuilder::lastName);\n```\nAt the end, when all the fields have been read, we can use `map` to transform the builder in the `Viking` instance : \n\n```java\nJsonRead\u003cViking\u003e vikingJsonRead = vikingBuilderJsonReadStep2.map(b -\u003e b.build());\n```\n\nThe same with method reference : \n\n```java\nJsonRead\u003cViking\u003e vikingJsonRead = vikingBuilderJsonReadStep2.map(VikingBuilder::build);\n```\n\nWiring all together we'll have :\n\n```java\npublic static JsonRead\u003cViking\u003e reader() {\n    return _string(\"firstName\", Viking.builder()::firstName)\n            .and(_string(\"lastName\"), Viking.VikingBuilder::lastName)\n            .and(_opt(\"city\", _string()), Viking.VikingBuilder::city)\n            .map(Viking.VikingBuilder::build);\n} \n```\n\nWe provide readers for :\n* `String`: `_string(...)`\n* `Integer`: `_int(...)`\n* `Long`: `_long(...)`\n* `Boolean`: `_boolean(...)`\n* `BigDecimal`: `_bigDecimal(...)`\n* `Enum`: `_enum(...)`\n* `LocalDate`: `_localDate(...)`, `_isoLocalDate(...)`\n* `LocalDateTime`: `_localDateTime(...)`, `_isoLocalDateTime(...)`\n* `Option`: `_opt(...)`\n* `List`: `_list(...)`\n* `Set`: `_set(...)`\n* Generic read at path: `__()`\n\n## Json Write\n\nA json write is :\n\n```java\n@FunctionalInterface\npublic interface JsonWrite\u003cT\u003e {\n    JsonNode write(T value);\n}\n```\n\nWith a lambda you can define a write as : \n\n```java\nJsonWrite\u003cString\u003e strWrite = str -\u003e new TextNode(str);\n```\n\nTo define a JSON object or arrays there is helpers so for the `Viking` POJO a write look like : \n\n```java\npublic static JsonWrite\u003cViking\u003e writer() {\n    return viking -\u003e Json.obj(\n            $(\"firstName\", viking.firstName),\n            $(\"lastName\", viking.lastName),\n            $(\"city\", viking.city)\n    );\n}\n``` \n\nJust as we do for the readers, we provide writers common types : \n\n* `String`: `$string()`\n* `Integer`: `$int()`\n* `Long`: `$long()`\n* `Boolean`: `$boolean()`\n* `BigDecimal`: `_bigdecimal()`\n* `Enum`: `$enum()`\n* `LocalDate`: `$localdate()`\n* `LocalDateTime`: `$localdatetime()`\n* `Traversable`: `$list(JsonWrite\u003cT\u003e)`\n* Jackson array: `Json.newArray()` or `Json.arr(...nodes)`\n\n## JsonFormat\n\nA json format is the combinaison of a `JsonRead` and a `JsonWrite`. You can create a format using of: \n\n```java\nJsonFormat\u003cViking\u003e format() {\n    return JsonFormat.of(reader(), writer());\n}\n```\n\n## Parsing json and converting it to POJOs \n\nAt the end with the following definition : \n\n```java\n@FieldNameConstants\n@Builder\n@AllArgsConstructor\npublic class Viking {\n\n    // A JsonFormat is both JsonRead and JsonWrite\n    public static JsonFormat\u003cViking\u003e format() {\n        return JsonFormat.of(reader(), writer());\n    }\n\n    public static JsonRead\u003cViking\u003e reader() {\n        return _string(\"firstName\", Viking.builder()::firstName)\n                .and(_string(\"lastName\"), Viking.VikingBuilder::lastName)\n                .and(_opt(\"city\", _string()), Viking.VikingBuilder::city)\n                .map(Viking.VikingBuilder::build);\n    }\n\n    public static JsonWrite\u003cViking\u003e writer() {\n        return viking -\u003e Json.obj(\n                $(\"firstName\", viking.firstName),\n                $(\"lastName\", viking.lastName),\n                $(\"city\", viking.city)\n        );\n    }\n\n    public final String firstName;\n    public final String lastName;\n    public final Option\u003cString\u003e city;\n}\n``` \n\nWe can do : \n\n```java\n\nViking viking = Viking.builder()\n        .firstName(\"Ragnar\")\n        .lastName(\"Lodbrock\")\n        .city(Some(\"Kattegat\"))\n        .build();\n\nJsonNode jsonNode = Json.toJson(viking, Viking.format());\nString stringify = Json.stringify(jsonNode);\n\nJsonNode parsed = Json.parse(stringify);\n\nJsResult\u003cViking\u003e vikingJsResult = Json.fromJson(parsed, Viking.format());\n\n```\n\n## Handling `sum types` or polymorphism \n\nSometime you need to serialize or deserialize sum types. For example : \n\n```java\n\npublic interface Animal { }\n\n@Builder\n@Value\npublic class Dog implements Animal {\n    String name;\n}\n\n@Builder\n@Value\npublic class Cat implements Animal {\n    String name;\n}\n\n```\n\nIn the following example, there's also different version of the json object serialization. \nThe parsing is done using two fields `type` and `version`: \n \n * `type` is the type of the animal: dog or cat \n * `version` is the version of it's JSON representation  \n    \nHere, we have two versions of the dog JSON, one with the `name` in the field `legacyName` (the `v1` version) and one with the `name` in the field `name` (the `v2`version).  \n\nThe readers are the followings :\n\n```java\nJsonRead\u003cAnimal\u003e dogJsonReadV1 =\n        _string(\"legacyName\", Dog.builder()::name)\n            .map(Dog.DogBuilder::build);\n\nJsonRead\u003cAnimal\u003e dogJsonReadV2 =\n        _string(\"name\", Dog.builder()::name)\n            .map(Dog.DogBuilder::build);\n\n\nJsonRead\u003cAnimal\u003e catJsonRead =\n        _string(\"name\", Cat.builder()::name)\n            .map(Cat.CatBuilder::build);\n``` \n\nNow to read an animal you can do this : \n```java\nJsonRead\u003cAnimal\u003e oneOfRead = JsonRead.oneOf(_string(\"type\"), _string(\"version\"), \"data\", List(\n        caseOf((t, v) -\u003e t.equals(\"dog\") \u0026\u0026 v.equals(\"v1\"), dogJsonReadV1),\n        caseOf((t, v) -\u003e t.equals(\"dog\") \u0026\u0026 v.equals(\"v2\"), dogJsonReadV2),\n        caseOf((t, v) -\u003e t.equals(\"cat\") \u0026\u0026 v.equals(\"v1\"), catJsonRead)\n));\n```\n\nOr with the `vavr` helpers : \n```java\nJsonRead\u003cAnimal\u003e oneOfRead = JsonRead.oneOf(_string(\"type\"), _string(\"version\"), \"data\", List(\n        caseOf($Tuple2($(\"dog\"), $(\"v1\")), dogJsonReadV1),\n        caseOf($Tuple2($(\"dog\"), $(\"v2\")), dogJsonReadV2),\n        caseOf($Tuple2($(\"cat\"), $(\"v1\")), catJsonRead)\n));\n```\n\n## Json schema \n\n`JsonRead` exposes a JSON schema https://json-schema.org/. This could be usefull when you have to share the schema to other teams. \n\nIn the case you need to override or enrich the schema, there is some helpers to do that :\n\n```java\nJsonRead.ofRead(oneOfRead, JsonSchema.emptySchema()\n    .title(\"A title\")\n    .description(\"Blah blah blah\")\n    // ...\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaif%2Ffunctional-json","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaif%2Ffunctional-json","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaif%2Ffunctional-json/lists"}