https://github.com/muukii/grain
A data serialization template language in Swift
https://github.com/muukii/grain
dsl json swift
Last synced: about 2 months ago
JSON representation
A data serialization template language in Swift
- Host: GitHub
- URL: https://github.com/muukii/grain
- Owner: muukii
- License: apache-2.0
- Created: 2022-10-17T14:55:56.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-11-15T15:44:07.000Z (over 2 years ago)
- Last Synced: 2025-02-28T07:14:38.000Z (2 months ago)
- Topics: dsl, json, swift
- Language: Swift
- Homepage: https://muukii.github.io/Grain/documentation/graindescriptor/
- Size: 1.47 MB
- Stars: 8
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Grain
A data serialization template language in Swift
Describing data in Swift using DSL in `.swift` file then the command line application renders it into any format like JSON.
## Motivation
In writing JSON or something else like that, we may be exhausted in describing repeatedly.
We often think somehow it could be done effectively like a programming language.
[jsonnet](https://jsonnet.org/) is one of the preprocessors solving that.
Grain aims to achieve such a function in Swift language.Create a swift file, then write data, and render it by using Grain tools.
The advantages of using Swift are:
- writing data and data structure like thinking in SwiftUI
- type checking by Swift compiler
- validating the data## Inspireations of using Grain
- Writing OpenAPI Specification and using by rendering
- Writing JSON Schema
- Writing a complex XcodeGen configuration beyond its built-in expressions
- Managing mocking APNs files
- and more there are a lot of opportunities of using Grain where are using JSON.## Naming
serialization -> cereal -> grain
## Installation
From [mint 🌱](https://github.com/yonaskolb/Mint)
```
$ mint install muukii/Grain
```From make
```
$ make install
```other installation ways will be added in the future
## Overview
```sh
$ grain
``````
$ grain ...
``````
$ grain ... --output /path/to/output/
```It writes results into given path using same name.
`Schema.swift` -> `Schema.json`## Instructions
### A basic case
Creates Data.swift describing data
```swift
import GrainDescriptorserialize {
GrainObject {
GrainMember("value") {
1
}
for i in 0..<10 {
GrainMember("key_\(i)") {
i
}
}
}
}```
Renders Data.swift as JSON in Terminal
```sh
$ grain Data.swift
{
"key_0" : 0,
"key_1" : 1,
"key_2" : 2,
"key_3" : 3,
"key_4" : 4,
"key_5" : 5,
"key_6" : 6,
"key_7" : 7,
"key_8" : 8,
"key_9" : 9,
"value" : 1
}
```### `serialize` function is the way to create output
`serialize` function declares what renders into data.
It also has implicit parameters to customize how to encode and how to save as files.```swift
serialize {
// describe what you serialize
}
```output parameter accepts how result should be output.
```swift
serialize(output: .stdout) { ... }
```prints out into stdout
```swift
serialize(output: .file) { ... }
```write into file in the same directory of source, or specified output directory (using `--output` option)
```swift
serialize(output: .file(.named("")) { ... }
```writing file as well as above but uses different name by given name.
It allows us to define multiple times.
This makes files using given name.```swift
serialize(output: .file(.named("pattern-1"))) {
}serialize(output: .file(.named("pattern-2"))) {
}
```### Creating component and composing them to describe data efficiently
In Component.swift
```swift
import GrainDescriptorserialize {
GrainObject {
GrainMember("data") {
Results(records: [
.init(name: "A", age: 1),
.init(name: "B", age: 2),
])
}
}
}// MARK: - Components
struct Record: GrainView {
let name: String
let age: Int
var body: some GrainView {
GrainObject {
GrainMember("name") {
name
}
GrainMember("age") {
age
}
}
}
}struct Results: GrainView {
let records: [Record]
var body: some GrainView {
GrainObject {
GrainMember("results") {
records
}
}
}
}
``````sh
$ grain Component.swift
{
"data" : {
"results" : [
{
"age" : 1,
"name" : "A"
},
{
"age" : 2,
"name" : "B"
}
]
}
}
```### Use async/await
the template file allows writing async/await operation.
`GrainDescriptor` is including `Alamofire` as built-in.
For instance, we could create a serizalized data using networking like fetching data.```swift
import GrainDescriptorlet response = try await AF.request("https://httpbin.org/get").serializingString().value
serialize {
GrainObject {
GrainMember("result") {
response
}
}
}
```## Showcases
OpenAPI Specification
```swift
import GrainDescriptorserialize {
Endpoint(methods: [
.init(
method: .get,
summary: "Hello",
description: "Hello Get Method",
operationID: "id",
tags: ["Awesome API"]
)
])
}// MARK: - Components
public struct Endpoint: GrainView {
public var methods: [Method]
public var body: some GrainView {
GrainObject {
for method in methods {
GrainMember(method.method.rawValue) {
method
}
}
}
}
}public struct Method: GrainView {
public enum HTTPMethod: String {
case get
case post
case put
case delete
}
public var method: HTTPMethod
public var summary: String
public var description: String
public var operationID: String
public var tags: [String]
public var body: some GrainView {
GrainObject {
GrainMember("operationId") { operationID }
GrainMember("description") { description }
GrainMember("summary") { summary }
GrainMember("tags") { tags }
}
}
}
``````sh
$ grain endpoints.swift
{
"get" : {
"description" : "Hello Get Method",
"operationId" : "id",
"summary" : "Hello",
"tags" : [
"Awesome API"
]
}
}
```