{"id":16064927,"url":"https://github.com/muukii/grain","last_synced_at":"2026-03-14T02:01:57.465Z","repository":{"id":61812540,"uuid":"552999640","full_name":"muukii/Grain","owner":"muukii","description":"A data serialization template language in Swift","archived":false,"fork":false,"pushed_at":"2022-11-15T15:44:07.000Z","size":1546,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-28T07:14:38.000Z","etag":null,"topics":["dsl","json","swift"],"latest_commit_sha":null,"homepage":"https://muukii.github.io/Grain/documentation/graindescriptor/","language":"Swift","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/muukii.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":"2022-10-17T14:55:56.000Z","updated_at":"2022-12-27T21:20:39.000Z","dependencies_parsed_at":"2022-10-21T17:00:26.921Z","dependency_job_id":null,"html_url":"https://github.com/muukii/Grain","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muukii%2FGrain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muukii%2FGrain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muukii%2FGrain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muukii%2FGrain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/muukii","download_url":"https://codeload.github.com/muukii/Grain/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243902856,"owners_count":20366370,"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":["dsl","json","swift"],"created_at":"2024-10-09T05:10:13.578Z","updated_at":"2026-03-14T02:01:57.344Z","avatar_url":"https://github.com/muukii.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Grain\n\nA data serialization template language in Swift\n\nDescribing data in Swift using DSL in `.swift` file then the command line application renders it into any format like JSON.\n\n## Motivation\n\nIn writing JSON or something else like that, we may be exhausted in describing repeatedly.  \nWe often think somehow it could be done effectively like a programming language.  \n[jsonnet](https://jsonnet.org/) is one of the preprocessors solving that.  \nGrain aims to achieve such a function in Swift language.\n\nCreate a swift file, then write data, and render it by using Grain tools.\n\nThe advantages of using Swift are:\n- writing data and data structure like thinking in SwiftUI\n- type checking by Swift compiler\n- validating the data\n\n## Inspireations of using Grain\n\n- Writing OpenAPI Specification and using by rendering\n- Writing JSON Schema\n- Writing a complex XcodeGen configuration beyond its built-in expressions\n- Managing mocking APNs files\n- and more there are a lot of opportunities of using Grain where are using JSON.\n\n## Naming\n\nserialization -\u003e cereal -\u003e grain\n\n## Installation\n\nFrom [mint 🌱](https://github.com/yonaskolb/Mint)\n\n```\n$ mint install muukii/Grain\n```\n\nFrom make\n```\n$ make install\n```\n\nother installation ways will be added in the future\n\n## Overview\n\n```sh\n$ grain \u003ctemplate file\u003e\n```\n\n```\n$ grain \u003cFile 1\u003e \u003cFile 2\u003e ...\n```\n\n```\n$ grain \u003cFile 1\u003e \u003cFile 2\u003e ... --output /path/to/output/\n```\n\nIt writes results into given path using same name.  \n`Schema.swift` -\u003e `Schema.json`\n\n## Instructions\n\n### A basic case\n\nCreates Data.swift describing data\n\n```swift\nimport GrainDescriptor\n\nserialize {\n  \n  GrainObject {\n    GrainMember(\"value\") {\n      1\n    }\n    \n    for i in 0..\u003c10 {\n      GrainMember(\"key_\\(i)\") {\n        i\n      }\n    }\n  }\n  \n}\n\n```\n\nRenders Data.swift as JSON in Terminal\n```sh\n$ grain Data.swift\n{\n  \"key_0\" : 0,\n  \"key_1\" : 1,\n  \"key_2\" : 2,\n  \"key_3\" : 3,\n  \"key_4\" : 4,\n  \"key_5\" : 5,\n  \"key_6\" : 6,\n  \"key_7\" : 7,\n  \"key_8\" : 8,\n  \"key_9\" : 9,\n  \"value\" : 1\n}\n```\n\n### `serialize` function is the way to create output\n\n`serialize` function declares what renders into data.  \nIt also has implicit parameters to customize how to encode and how to save as files.\n\n```swift\nserialize {\n  // describe what you serialize\n}\n```\n\noutput parameter accepts how result should be output.\n\n```swift\nserialize(output: .stdout) { ... }\n```\n\nprints out into stdout\n\n```swift\nserialize(output: .file) { ... }\n```\n\nwrite into file in the same directory of source, or specified output directory (using `--output` option)\n\n```swift\nserialize(output: .file(.named(\"\u003cfile_name\u003e\")) { ... }\n```\n\nwriting file as well as above but uses different name by given name.\n\nIt allows us to define multiple times.  \nThis makes files using given name.\n\n```swift\nserialize(output: .file(.named(\"pattern-1\"))) {\n  \n}\n\nserialize(output: .file(.named(\"pattern-2\"))) {\n  \n}\n```\n\n### Creating component and composing them to describe data efficiently\n\nIn Component.swift\n```swift\nimport GrainDescriptor\n\nserialize {\n  \n  GrainObject {\n    GrainMember(\"data\") {\n      Results(records: [\n        .init(name: \"A\", age: 1),\n        .init(name: \"B\", age: 2),\n      ])\n    }\n  }\n  \n}\n\n// MARK: - Components\n\nstruct Record: GrainView {\n  \n  let name: String\n  let age: Int\n  \n  var body: some GrainView {\n    GrainObject {\n      GrainMember(\"name\") {\n        name\n      }\n      GrainMember(\"age\") {\n        age\n      }\n    }\n  }\n  \n}\n\nstruct Results: GrainView {\n  \n  let records: [Record]\n  \n  var body: some GrainView {\n    GrainObject {\n      GrainMember(\"results\") {\n        records\n      }\n    }\n  }\n  \n}\n```\n\n```sh\n$ grain Component.swift\n{\n  \"data\" : {\n    \"results\" : [\n      {\n        \"age\" : 1,\n        \"name\" : \"A\"\n      },\n      {\n        \"age\" : 2,\n        \"name\" : \"B\"\n      }\n    ]\n  }\n}\n```\n\n### Use async/await\n\nthe template file allows writing async/await operation.    \n`GrainDescriptor` is including `Alamofire` as built-in.  \nFor instance, we could create a serizalized data using networking like fetching data.\n\n```swift\nimport GrainDescriptor\n\nlet response = try await AF.request(\"https://httpbin.org/get\").serializingString().value\n\nserialize {\n  \n  GrainObject {  \n    GrainMember(\"result\") {\n      response\n    }\n  }\n  \n}\n```\n\n## Showcases\n\n\u003cdetails\u003e\n    \u003csummary\u003eOpenAPI Specification\u003c/summary\u003e\n \n```swift\nimport GrainDescriptor\n\nserialize {\n  Endpoint(methods: [\n    .init(\n      method: .get,\n      summary: \"Hello\",\n      description: \"Hello Get Method\",\n      operationID: \"id\",\n      tags: [\"Awesome API\"]\n    )\n  ])\n}\n\n// MARK: - Components\n\npublic struct Endpoint: GrainView {\n  \n  public var methods: [Method]\n  \n  public var body: some GrainView {\n    GrainObject {\n      for method in methods {\n        GrainMember(method.method.rawValue) {\n          method\n        }\n      }\n    }\n  }\n}\n\npublic struct Method: GrainView {\n  \n  public enum HTTPMethod: String {\n    case get\n    case post\n    case put\n    case delete\n  }\n  \n  public var method: HTTPMethod\n  public var summary: String\n  public var description: String\n  public var operationID: String\n  public var tags: [String]\n  \n  public var body: some GrainView {\n    GrainObject {\n      GrainMember(\"operationId\") { operationID }\n      GrainMember(\"description\") { description }\n      GrainMember(\"summary\") { summary }\n      GrainMember(\"tags\") { tags }\n    }\n  }\n}\n```\n\n```sh\n$ grain endpoints.swift\n{\n  \"get\" : {\n    \"description\" : \"Hello Get Method\",\n    \"operationId\" : \"id\",\n    \"summary\" : \"Hello\",\n    \"tags\" : [\n      \"Awesome API\"\n    ]\n  }\n}\n```\n\n\u003c/details\u003e\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmuukii%2Fgrain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmuukii%2Fgrain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmuukii%2Fgrain/lists"}