{"id":15789782,"url":"https://github.com/danielgtaylor/sdt","last_synced_at":"2025-03-14T13:30:31.042Z","repository":{"id":46323935,"uuid":"418250995","full_name":"danielgtaylor/sdt","owner":"danielgtaylor","description":"Structured Data Templates","archived":false,"fork":false,"pushed_at":"2021-12-16T17:31:35.000Z","size":228,"stargazers_count":4,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-05T22:03:23.750Z","etag":null,"topics":["hacktoberfest","json","json-schema","openapi3","structured-data-templates","template-language","yaml"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielgtaylor.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":"2021-10-17T20:42:58.000Z","updated_at":"2022-05-12T09:36:48.000Z","dependencies_parsed_at":"2022-09-05T13:20:28.964Z","dependency_job_id":null,"html_url":"https://github.com/danielgtaylor/sdt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgtaylor%2Fsdt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgtaylor%2Fsdt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgtaylor%2Fsdt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgtaylor%2Fsdt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielgtaylor","download_url":"https://codeload.github.com/danielgtaylor/sdt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243584016,"owners_count":20314680,"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":["hacktoberfest","json","json-schema","openapi3","structured-data-templates","template-language","yaml"],"created_at":"2024-10-04T22:03:32.552Z","updated_at":"2025-03-14T13:30:30.701Z","avatar_url":"https://github.com/danielgtaylor.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Structured Data Templates\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/danielgtaylor/sdt.svg)](https://pkg.go.dev/github.com/danielgtaylor/sdt) [![Go Report Card](https://goreportcard.com/badge/github.com/danielgtaylor/sdt)](https://goreportcard.com/report/github.com/danielgtaylor/sdt) ![Build Status](https://github.com/danielgtaylor/sdt/actions/workflows/test.yaml/badge.svg?branch=main) [![codecov](https://codecov.io/gh/danielgtaylor/sdt/branch/main/graph/badge.svg?token=KB0QD0H6HP)](https://codecov.io/gh/danielgtaylor/sdt) [![VS Code Extension](https://img.shields.io/badge/vscode-extension-blue)](https://marketplace.visualstudio.com/items?itemName=danielgtaylor.structured-data-templates)\n\nStructured data templates are a templating engine that takes a simplified set of input parameters and transforms them into a complex structured data output. Both the inputs and outputs can be validated against a schema.\n\nBe sure to check out the [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=danielgtaylor.structured-data-templates) for syntax highlighting and schema validation as you type.\n\nThe goals of this project are to:\n\n1. Provide a simple format: it's just JSON/YAML!\n2. Give enough tools to be useful:\n   - Interpolation `${my_value}` \u0026 `${num / 2 \u003e= 5}`\n   - Branching (if/then/else)\n   - Looping (for/each)\n3. Guarantee structural correctness\n   - The structured data template is valid JSON / YAML\n   - The input parameters are valid JSON / YAML\n   - The output of the template is guaranteed to produce valid JSON\n4. Provide tools for semantic correctness via schemas\n   - The input types and values pass the schema\n   - The template will produce output that should pass the schema\n   - The output of the template after rendering passes the schema\n\n## Structure\n\nA structured data template document is made of two parts: schemas and a template. The schemas define the allowable input/output structure while the template defines the actual rendered output. An example document might look like:\n\n```yaml\nschemas:\n  # Dialect selects the default JSON Schema version\n  dialect: openapi-3.1\n  input:\n    # Input schema goes here\n    type: object\n    properties:\n      name:\n        type: string\n        default: world\n  output:\n    # Output schema goes here, also supports refs:\n    $ref: https://api.example.com/openapi.json#components/schemas/Greeting\ntemplate:\n  # Templated output structure goes here\n  greeting: Hello, ${name}!\n```\n\n## Installation\n\nYou can install via:\n\n```sh\n# Get the `sdt` command\n$ go get -u github.com/danielgtaylor/sdt/cmd/...\n\n# Install as a library\n$ go get -u github.com/danielgtaylor/sdt\n```\n\n## Example\n\nYou can run the example like so:\n\n```sh\n# Validate the template\n$ sdt validate ./samples/greeting.yaml\n\n# Generate an example params file\n$ sdt example -f yaml ./samples/hello/hello.yaml \u003eparams.yaml\n\n# Render by passing in a file\n$ sdt render ./samples/hello/hello.yaml \u003cparams.yaml\n{\n  \"greeting\": \"Hello, SDT!\"\n}\n\n# Render by using CLI shorthand syntax\n$ sdt render ./samples/greeting.yaml name: Alice\n{\n  \"greeting\": \"Hello, Alice!\"\n}\n```\n\nInput params for rendering can be passed via stdin as JSON/YAML and/or via command line arguments as [CLI shorthand syntax](https://github.com/danielgtaylor/shorthand#readme).\n\n## Schemas\n\nJSON Schema is used for all schemas. It defaults to JSON Schema 2020-12 but can be overridden via the `$schema` key or using `dialect` in the structured data template document like above. Available dialects:\n\n- `openapi-3.0`\n- `openapi-3.1`\n- `https://json-schema.org/draft/2020-12/schema`\n- `https://json-schema.org/draft/2019-09/schema`\n- `https://json-schema.org/draft-07/schema`\n- `https://json-schema.org/draft-06/schema`\n- `https://json-schema.org/draft-04/schema`\n\nThe input schema describes the input parameters and the template will not render unless the passed parameters validate using the input schema. It also lets you set defaults for the input parameters, which default to `nil` if not passed.\n\nThe output schema describes the template's output structure. The validator is capable of understanding branches \u0026 loops to ensure that the output is semantically valid regardless of which path is taken during rendering.\n\n## Template Language Specification\n\nA template is just JSON/YAML. For example:\n\n```yaml\nhello: world\n```\n\nThat is a valid static template. Nothing will change when rendered, which is not very useful. Normally, when a template is rendered, it is passed parameters, and these are used for interpolation, branching, and looping, which are specified using special syntax in strings or keywords as object property names:\n\n- Interpolation: `${...}`\n- Branching: `$if`, `$then`, `$else`\n- Looping: `$for`, `$as`, `$each`\n- Special operations: `$flatten`\n\nThese features make use of a basic expression language.\n\n### Expressions\n\nString interpolation, branching conditions, and loop variable selection all use an expression language. This allows you to make simple comparisons of the parameter context data. Examples:\n\n- `foo \u003e 50`\n- `item.bars.length \u003c= 5 or my_override`\n- `\"sdt\" in name`\n- `name startsWith \"sdt\"`\n- `\"foo\" in my_array`\n- `loop.index + 1`\n\nSee [danielgtaylor/mexpr syntax](https://github.com/danielgtaylor/mexpr#syntax) for details.\n\n### String Interpolation\n\nString interpolation is the act of replacing the contents of `${...}` within strings, where `...` corresponds to an expression that makes use of input parameters. For example:\n\n```yaml\nhello: ${name}\n```\n\nIf passed `{\"name\": \"Alice\"}` as parameters this would render:\n\n```json\n{\n  \"hello\": \"Alice\"\n}\n```\n\nWhenever the string is just one `${...}` statement it will use whatever type it evaluates to in the result, so you are not limited to just strings. If the expression result is `nil`, then the property/item is not included in the rendered output.\n\nIt's also possible to add static text or multiple interpolation expressions in a single value:\n\n```yaml\nhello: Greetings, ${name}!\n```\n\nGiven the same input that would result in:\n\n```json\n{\n  \"hello\": \"Greetings, Alice!\"\n}\n```\n\n#### Tricks\n\n- Force a string output by using more than one expression: `${my_number}${\"\"}`\n\n### Branching\n\nBranching allows one of multiple paths to be followed in the template at rendering time based on the result of an expression. The special properties `$if`, `$then`, and `$else` are used for this. For example:\n\n```yaml\nfoo:\n  $if: ${value \u003e 5}\n  $then: I am big\n  $else: I am small\n```\n\nIf rendered with `{\"value\": 1}` the result will be:\n\n```json\n{\n  \"foo\": \"I am small\"\n}\n```\n\nNotice that the special properties are completely removed and replaced with the contents of either the `$then` or `$else` clauses. So while in the _template_ `foo` is an object, the end result is that `foo` is a string and would pass the output schema.\n\nIf the expression is false and no `$then` is given, then the property is removed from the result.\n\n### Looping\n\nLooping allows an array of inputs to be expanded into the rendered output using a per-item template. The `$for`, `$as`, and `$each` special properties are used for this. For example:\n\n```yaml\nsquares:\n  $for: ${numbers}\n  $each: ${item * item}\n```\n\nIf rendered with `{\"numbers\": [1, 2, 3]}` the result will be:\n\n```json\n{\n  \"squares\": [2, 4, 9]\n}\n```\n\nThe `$as` property controls the name of the variable holding the current item, which defaults to `item`. A local variable `loop` is also set, which includes an `index`, and whether the item is the `first` or `last` in the array. If using `$as` then the `loop` variable is named `loop_` + the `$as` value. This allows nested loops to access both their own and outer scope's loop variables. For example:\n\n```yaml\nthings:\n  $for: ${things}\n  $as: thing\n  $each:\n    id: ${loop_thing.index}-${thing.name}\n    tags:\n      $for: ${tags}\n      $as: tag\n      $each: ${loop_thing.index}-${loop_tag.index}-${tag}\n```\n\nGiven:\n\n```json\n{\n  \"things\": [{ \"name\": \"Alice\" }, { \"name\": \"Bob\" }],\n  \"tags\": [\"big\", \"small\"]\n}\n```\n\nYou would get as output:\n\n```json\n{\n  \"things\": [\n    {\n      \"id\": \"0-Alice\",\n      \"tags\": [\"0-0-big\", \"0-1-small\"]\n    },\n    {\n      \"id\": \"1-Bob\",\n      \"tags\": [\"1-0-big\", \"1-1-small\"]\n    }\n  ]\n}\n```\n\n### Flatten\n\nThe `$flatten` special operator takes an array of arrays and flattens them one level into a single array. This can be useful for a number of scenarios like:\n\n- Adding default items to a `$for` loop output\n- Having one item of a `$for` clause generate multiple outputs\n\nFor a simple example:\n\n```yaml\nmy_array:\n  $flatten:\n    - [0, 1, 2]\n    - [3, 4, 5]\n    - [6, 7, 8]\n```\n\nThis would result in:\n\n```json\n{\n  \"my_array\": [0, 1, 2, 3, 4, 5, 6, 7, 8]\n}\n```\n\nMore complex scenarios are possible when combined with `$for` clauses:\n\n```yaml\n# Loop through the items twice, generating one item at a time.\nappended_array:\n  $flatten:\n    - $for: ${items}\n      $each: ${item}\n    - $for: ${items}\n      $each: ${item * item}\n# Loop through the items once, generating a list for each item.\nmerged_array:\n  $flatten:\n    $for: ${items}\n    $each:\n      - ${item}\n      - ${item * item}\n```\n\nIf given:\n\n```json\n{\n  \"items\": [2, 3, 4]\n}\n```\n\nYou would get:\n\n```json\n{\n  \"appended_array\": [2, 3, 4, 4, 9, 16],\n  \"merged_array\": [2, 4, 3, 9, 4, 16]\n}\n```\n\n## Open Questions\n\n1. Should we support macros? Could be done with `$ref` in the template, and we could add a top-level `macros` or `definitions` for document-local refs. They would be drop-in only, no calling with arguments, but would render based on the current params context.\n\n2. Should `nil` results from interpolation be rendered in the final output? Example: `name: ${name}` and what if `name` is `nil`?\n\n3. Support for constants? Values that should always be present in the params that can contain complex and reusable data for the template?\n\n4. Ability to sort `$for` loop output based on some expr?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgtaylor%2Fsdt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielgtaylor%2Fsdt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgtaylor%2Fsdt/lists"}