{"id":13801067,"url":"https://github.com/lordnull/rec2json","last_synced_at":"2025-09-07T15:33:27.056Z","repository":{"id":4678608,"uuid":"5825119","full_name":"lordnull/rec2json","owner":"lordnull","description":"Compile erlang record definitions into modules to convert them to/from json easily.","archived":false,"fork":false,"pushed_at":"2024-07-02T18:48:46.000Z","size":377,"stargazers_count":46,"open_issues_count":3,"forks_count":23,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-12-26T14:10:29.727Z","etag":null,"topics":["erlang","json","json-converter"],"latest_commit_sha":null,"homepage":null,"language":"Erlang","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/lordnull.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-09-15T23:20:57.000Z","updated_at":"2023-09-08T16:35:19.000Z","dependencies_parsed_at":"2024-11-07T13:10:49.964Z","dependency_job_id":null,"html_url":"https://github.com/lordnull/rec2json","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lordnull%2Frec2json","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lordnull%2Frec2json/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lordnull%2Frec2json/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lordnull%2Frec2json/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lordnull","download_url":"https://codeload.github.com/lordnull/rec2json/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232227797,"owners_count":18491734,"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":["erlang","json","json-converter"],"created_at":"2024-08-04T00:01:19.200Z","updated_at":"2025-01-02T17:17:54.341Z","avatar_url":"https://github.com/lordnull.png","language":"Erlang","readme":"# rec2json\n[![Build Status](https://travis-ci.org/lordnull/rec2json.png?branch=master)](https://travis-ci.org/lordnull/rec2json)\n\n## Overview\n\nRec2json is a parse transform that takes a module which defines a record of\nthe same name and adds to_json, from_json, and introspection functions. The\nto_json and from_json convert to and from the map based format used by jsx and\nother json encoding and decoding libraries.\n\n## Features\n\n* Uses a parse transform.\n* Type checking on json -\u003e record conversion using primitive built-in types.\n* Type checking can be extended to include user defined types.\n* Atom 'undefined' fields in records optionally skipped or set to null.\n* Atom 'null' in json optionally converted to 'undefined'.\n* Post processing options on record -\u003e json convertion.\n* Seed json -\u003e record conversion with a record.\n* Nested json -\u003e record and record -\u003e json conversions for other records\nthat have been compiled using rec2json.\n* Generated module has accessor functions for fields and field_names for\nlist of fields in the record.\n* Above feature can be surpressed, and is careful by default.\n* Generated module exports functions to examine structure and types for a\nrecord.\n\n## Compiling\n\n    make\n\nTo run tests:\n\n    make tests\n\n    make check\n\n## Use\n\nThe rec2json parse_transform looks for a record with the same name as the\nmodule. It doesn't matter whether the record was defined directly in the\nmodule or in an include file.\n\n    -compile([{parse_transform, rec2json}]).\n\nThe transformed modules depend on the rec2json application's modules.\n\nThe records are unchanged and can be used normally.\n\nOptions are passed to the rec2json parse transform through compile options.\nThe parse transform checks for the key 'rec2json' in the compile options.\nThe value is expected to be a proplist.\n\nOptions can also be passed in on a per-module basis by adding one or more\n`rec2json` module attributes. A `rec2json` module attribute can either be a\ntuple for one option, or a list of option tuples. They all get mashed together.\nUsing the same option more than once has undefined behavior.\n\nOptions are:\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eOption\u003c/th\u003e \u003cth\u003eDefault: Values\u003c/th\u003e \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003egenerate_accessors\u003c/td\u003e \u003ctd\u003etrue : boolean()\u003c/td\u003e \u003ctd\u003eIf set to\ntrue, functions for accessing the fields of a record are exported and\ncreated. If set to false, they are not created nor exported.\u003c/td\u003e\n  \u003c/tr\u003e\n\t\u003ctr\u003e\n\t\t\u003ctd\u003egenerate_setters\u003c/td\u003e \u003ctd\u003etrue : boolean()\u003c/td\u003e \u003ctd\u003e If set to\ntrue, functions for setting the fields of a record are created and\nexported. These are of the form Field(NewVal, Record). If set to false, they\nare not created nor exported.\n\t\t\u003c/td\u003e\n\t\u003c/tr\u003e\n\t\u003ctr\u003e\n\t\t\u003ctd\u003ecareful\u003c/td\u003e \u003ctd\u003etrue : boolean()\u003c/td\u003e \u003ctd\u003e If set to true,\nrec2json's parse transform avoids altering or adding functions that are\nalready defined in the module. This means you can override the default\nto_json/1 function to call to_json/2 with a specific set of options.\u003c/td\u003e\n\t\u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003egenerate_type\u003c/td\u003e \u003ctd\u003etrue : boolean()\u003c/td\u003e \u003ctd\u003e If set to\ntrue, a type is generated for the record, and that type is exported. In\naddition, a function is generated so that other rec2json records using the\nexported type work as expected.\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003etype_name\u003c/td\u003e \u003ctd\u003e ?MODULE : atom()\u003c/td\u003e \u003ctd\u003e If generate_type is\ntrue, this changes the type name and the function name for the conversion\nfunction.\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\nThe given examples use the following record and record defintion:\n\n```erlang\n-record(person {\n    name :: binary(),\n    age = 0 :: pos_integer(),\n    spouse :: #person{}\n}).\nRecord = #person{ name = \u003c\u003c\"John\"\u003e\u003e, age = 32, spouse = undefined }.\n```\n\n### to_json\n\nTo convert a record to a json structure:\n\n```erlang\nJson = person:to_json(Record).\n#{name := \u003c\u003c\"John\"\u003e\u003e, age := 32} = Json.\n```\n\nThe to_json function can take a list of mutators. Mutators are applied in\nthe order listed except {null_is_undefined}. Supported mutators are:\n\n* Turn undefined into null instead of skipping the field\n\n```erlang\nperson:to_json(Record, [{null_is_undefined}]).\n```\n\n* Add a property\n\n```erlang\nperson:to_json(Record, [{single, true}]).\nperson:to_json(Record, [#{single =\u003e true}).\nperson:to_json(Record, [#{single =\u003e true, employed =\u003e true}).\n```\n\n* Remove a property\n\n```erlang\nperson:to_json(Record, [age]).\n```\n\n* Modify based only on the json\n\n```erlang\nModFunc = fun(Json) -\u003e\n    case maps:find(spouse, Json) of\n        error -\u003e\n            Json#{single =\u003e true}\n        _ -\u003e\n            Json#{single =\u003e false}\n    end\nend.\nperson:to_json(Record, [ModFunc]).\n```\n\n* Modify based on both json and record\n\n```erlang\nModFunc = fun(Json, Record) -\u003e\n    case Record#person.spouse of\n        undefined -\u003e\n            Json#{single =\u003e true};\n        _ -\u003e\n            Json#{single =\u003e false}\n    end\nend.\nperson:to_json(Record, [ModFunc]).\n```\n\n### from_json\n\nConverting from a json structure to a record is just as simple:\n\n```erlang\n{ok, Record} = person:from_json(#{\n    \u003c\u003c\"name\"\u003e\u003e =\u003e \u003c\u003c\"John\"\u003e\u003e,\n    \u003c\u003c\"age\"\u003e\u003e =\u003e 32,\n    \u003c\u003c\"spouse\"\u003e\u003e =\u003e null\n}).\n```\n\nIt may be desirable to change 'null' into 'undefined' in the record:\n\n```erlang\n{ok, Record} = person:from_json(Json, [null_is_undefined]).\n```\n\nIt may be desirable to start with an existing record instead of creating\na new one:\n\n```erlang\n{ok, Record2} = person:from_json(Json, Record).\n{ok, Record2} = person:from_json(Record, Json).\n{ok, Record2} = person:from_json(Record, Json, [null_is_undefined]).\n```\n\nIf the json structure has a type that cannot be reconciled with a type\nspecified by the record definition, a list of fields with possible errors\nis returned. The record will have the data that was in the json structure.\nAn untyped record field is the same as having the type 'any()'. There are\nno warnings about missing properties in the json, they simply retain the\ndefault value of the record.\n\n```erlang\n{ok, Record, [age]} = person:from_json(#{\u003c\u003c\"age\"\u003e\u003e =\u003e \u003c\u003c\"32\"\u003e\u003e}).\n```\n\n### Including in a project\n\nIf all you are using is the parse_transform, simply add rec2json as a\nrequired application.\n\n## Type Checking and Conversion\n\nType conversion attempts to be as transparent and intuitive as possible.\nThere are some types that json does not represent directly, and some types\nthat have additional checking implemented.\n\nRecord fields that have atoms as types will have binary values in the json\nchecked. If the atom converted to the binary is equal to the json value, the\natom value is put into the record. When converting a record to json, atom\nvalues will be converted to binaries.\n\nLists have their types checked. If there is an invalid type, the invalid\ntype is placed in the list, but the warning message has the index of the\ninvalid type placed in the warning path list. For example:\n\n```erlang\n-record(list_holder, {\n    ids :: [integer()]\n}).\n\ntype_mismatch() -\u003e\n    Json = #{ids =\u003e [\u003c\u003c\"invalid\"\u003e\u003e, 3]},\n    {ok, Record, Warnings} = list_holder:from_json(Json),\n    #list_holder{ids = [\u003c\u003c\"invalid\"\u003e\u003e, 3]} = Record,\n    [[ids, 1]] = Warnings.\n```\n\nProplists will match the first record type. A warning is emitted if that\nrecord was not compiled using rec2json (eg: RecordName:from_json/2 is not\nan exported function). If the compilation emits warnings, the resulting warning\nlist has the field name prepended to each.  For example:\n\n```erlang\n-record(outer, {\n    in_field :: #inner{}\n}).\n-record(inner, {\n    count :: integer()\n}).\n\ntype_mismatch() -\u003e\n    Json = #{in_field =\u003e #{count =\u003e \u003c\u003c\"0\"\u003e\u003e}},\n    {ok, Record, Warnings} = outer:from_json(Json),\n    #outer{in_field = #inner{ count = \u003c\u003c\"0\"\u003e\u003e } } = Record,\n    [[in_filed, count]] = Warnings.\n```\n\nIf a record field is not typed, or has the type \"any()\", no warning is ever\nemitted for that field.\n\nType checking comes in two flavors: built-in, and user defined.\n\n### Built-in types\n\nCurrently defined types checked:\n\n* integer()\n* pos_integer()\n* non_neg_integer()\n* neg_integer()\n* float()\n* number()\n* boolean()\n* binary()\n* [supported_type()]\n* #record{} when record has record:from_json/2 exported\n* atom (note it is not atom())*\n* null when converting to undefined or back\n\nThe type `atom()` is not supported because a primary use case for rec2json\nis to convert untrusted data, such as an http post request. Converting\nuntrusted data into atoms can exhaust the erlang vm's atom table, or worse\nexhaust the machine's memory. Rec2json is, by default, safe.\n\nIt is still possible to apply a type to a record field so that json strings\nwill be converted to the equivalent atom using either a user defined type,\nor the provided `r2j_type:unsafe_atom()` type.\n\n### User defined types\n\nA user defined type is the same as an external type. When going to or from\njson, rec2json will check to see if there is a translation function\nmatching the module and type of the type given. A translation function\nshould have an arity 1 greater than the type has parameters. The function\nshould either return `{ok, NewVal}` or `error`.\n\nFor example, given the module:\n\n```erlang\n-module(type_example).\n-compile([{parse_transform, rec2json}]).\n\n% These two lines exist to satisfy dialyzer.\n-type point() :: {number(), number()}.\n-export_type([point/0]).\n\n-record(type_example, {\n\tsome_field :: module:function(arg1, arg2),\n\txy = {0, 0} :: type_example:point()\n}).\n\n-export([point/1]).\n\npoint({X,Y}) when is_number(X), is_number(Y) -\u003e\n\t{ok, [X,Y]};\n\npoint([X, Y]) when is_number(X), is_number(Y) -\u003e\n\t{ok, {X, Y}};\n\npoint(_) -\u003e\n\terror.\n```\n\nWhen using to_json or from_json, rec2json will check to see if `module`\nexports a function named `function` with arity 3. If it exists, rec2json\nwill call the function with the args listed in the type, and the current\nvalue of the field (either from the record or from the json) prepended to\nthe arguments.\n\nWhen used in `from_json`, returns `error`, and all other types have been\ntested, `{ok, Rec, Warnings}` is returned. When used in `to_json`, an\nerror is thrown.\n\n## Contributing\n\nFork and submit a pull request with relevant tests.\n","funding_links":[],"categories":["Text and Numbers"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flordnull%2Frec2json","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flordnull%2Frec2json","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flordnull%2Frec2json/lists"}