GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - magicdrive/kirke: Yet another JSON to Go Struct Converter

BranchesTags
Open more actions menu

Latest commit

 

History

120 Commits
120 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
cmd/kirke
cmd/kirke
 
 
docs
docs
 
 
internal
internal
 
 
misc
misc
 
 
scripts
scripts
 
 
.gitignore
.gitignore
 
 
LICENSE
LICENSE
 
 
Makefile
Makefile
 
 
README.md
README.md
 
 
cli_errors_e2e_test.go
cli_errors_e2e_test.go
 
 
docs_test.go
docs_test.go
 
 
exitcode_test.go
exitcode_test.go
 
 
generated_roundtrip_e2e_test.go
generated_roundtrip_e2e_test.go
 
 
generated_unmarshal_e2e_test.go
generated_unmarshal_e2e_test.go
 
 
go.mod
go.mod
 
 
goreg.toml
goreg.toml
 
 
hephaestus.yaml
hephaestus.yaml
 
 
install.sh
install.sh
 
 
main.go
main.go
 
 
makefile_build_test.go
makefile_build_test.go
 
 
pipe_e2e_test.go
pipe_e2e_test.go
 
 
repo_publish_test.go
repo_publish_test.go
 
 
staticcheck_ci_test.go
staticcheck_ci_test.go
 
 

Repository files navigation

Kirke

Yet another command-line tool for converting JSON strings into Golang struct definitions. It’s designed to be flexible, allowing for JSON input from both files and pipes, and it handles pointer types for nested structures when specified.

Features

  • Supports both inline and outline Golang structs definitions
  • Support options that change the interpretation of json null
  • Receives JSON strings directly or from a file
  • Outputs formatted Golang structs use gofmt
  • Option to define struct and array fields as pointers
  • Flexible with file extensions (no need for .json)
  • Option to disable the pager for larger outputs

Installation

You can install kirke using Homebrew (it also installs the Bash and Zsh completions):

brew install magicdrive/tap/kirke
brew upgrade kirke                             # later, to update it

Or clone the repository and build the binary (needs Go, see go.mod; the binary is build/kirke):

git clone https://github.com/magicdrive/kirke.git
cd kirke
make build

Pre-built archives for macOS (amd64, arm64), Linux (386, amd64, arm64) and Windows (386, amd64, arm64) are on the Releases page, with kirke_checksums.txt (SHA-256) to check them against.

Install script (macOS and Linux, amd64 and arm64)

Download the script, read it if you like, then run it with sh:

curl -fsSLO https://raw.githubusercontent.com/magicdrive/kirke/main/install.sh
sh install.sh                                  # the latest release, into ~/.local/bin
"$HOME/.local/bin/kirke" --version             # prints: kirke version vX.Y.Z
sh install.sh                                  # install the latest release
sh install.sh --version v1.3.0                 # install a given release (older ones too)
sh install.sh --upgrade                        # switch to the latest stable release
sh install.sh --upgrade --version v1.3.0       # switch to a given release (also a downgrade)
sh install.sh --dir "$HOME/.local/bin"         # choose the directory (this is the default)
sh install.sh --help
  • Where: $HOME/.local/bin/kirke by default; --dir DIR installs DIR/kirke and creates DIR if needed (spaces in DIR are fine). It never uses sudo and never changes PATH or shell start-up files: if the script says the directory is not in PATH, add it, e.g. export PATH="$HOME/.local/bin:$PATH" in ~/.profile. For a system directory such as /usr/local/bin, run it as a user who can write there (it does not ask for sudo itself).
  • Versions: without --version it installs the latest stable release (pre-releases are not picked). --version takes a release tag, v1.3.0 or 1.3.0, newer or older than the installed one; a version that does not exist is an error, never another version. --upgrade is the same run, named for what you mean. Running it again with the installed version changes nothing. Every kirke release can be installed (v0.0.1 and later).
  • Updating: run sh install.sh --upgrade again (with the same --dir if you used one).
  • Checks: the archive must match the SHA-256 listed in the release's checksum file (kirke_checksums.txt); a missing or malformed checksum file stops the install. Only the kirke binary is taken out of the archive, after its entries have been checked (no absolute paths, .., links or devices). The new binary must run and report the version asked for before it replaces the old one in a single rename: a failed or interrupted run leaves the installed kirke as it was.
  • Security: every request starts at github.com, over https, and redirects (GitHub sends the downloads to its asset host) are followed only to https. The checksum file comes from the same GitHub release as the archive, so it catches a broken or altered download, but not a release that was itself replaced on GitHub. For a stronger check, verify the archive's build provenance with the GitHub CLI before you trust it: gh attestation verify kirke_<version>_<os>_<arch>.tar.gz -R magicdrive/kirke (releases from v1.3.0 on have these attestations). The command above runs the script from the main branch; to run a fixed script, download it from a release tag instead (https://raw.githubusercontent.com/magicdrive/kirke/<tag>/install.sh; the tags from v1.5.0 on have it). Piping it straight into sh (curl ... | sh) also works, but then you run what you have not seen.
  • Homebrew: the script refuses Homebrew's directories (its prefix, e.g. /opt/homebrew, or /usr/local on an Intel Mac) and does not replace a symbolic link such as one Homebrew made. Keep one way of installing: with Homebrew, upgrade with brew upgrade kirke; the script warns if another kirke comes first in PATH.
  • Uninstall: delete the file: rm "$HOME/.local/bin/kirke" (or DIR/kirke).
  • Needs: a POSIX sh, curl (or GNU Wget 1.17+; BusyBox wget is refused because it does not check certificates, and wget2 is not supported), tar, gzip, and sha256sum, shasum or openssl. A proxy set in https_proxy is used. INSTALLER_TIMEOUT (seconds, default 60) is how long it waits for a connection or more data.
  • Windows: the script does not run on Windows (Git Bash, MSYS2 and Cygwin are refused). Use the kirke_<version>_windows_<arch>.tar.gz of the Releases page (it holds kirke.exe). Inside WSL, the Linux instructions apply.
  • Troubleshooting: not writable — choose a directory of your own with --dir; not in PATH — see above; does not run in DIR (noexec) — choose a directory on another file system; SHA-256 mismatch or rejected — nothing was installed; try again later and report it if it persists; release not found — check the version on the Releases page; inside the Homebrew prefix — use brew upgrade kirke, or another --dir.

Quick Start

curl -s https://api.github.com/repos/magicdrive/kirke | kirke

Zsh and Bash Completion file

git clone https://github.com/magicdrive/kirke.git
cd kirke
source ./misc/completions/kirke-completion.sh

Usage

kirke [OPTIONS] [ARGUMENTS]

Description

Receives a JSON string and outputs it as a Golang struct definition.

The JSON is taken from the first of: standard input with --pipe; --json; --file; standard input when it is not a terminal (a pipe or a redirected file). Without any JSON and without arguments, kirke prints the help message.

Options

Option Description Default
-h, --help Show this help message and exit
-v, --version Show version and exit
-n, --name <root-name> Specify the name of the root struct. Converts to camel case automatically. AutoGenerated
-f, --file <json-file> Specify the input file containing JSON. Accepts files without .json extension.
-a, --null-as <null-type-name> Specify the null-type name. Used to replace null type from json. any
-j, --json <json-string> Specify the JSON string to be converted to a struct.
-p, --pipe Receive JSON from a pipe instead of a direct argument above all else. false
--pointer-struct, --pointer <on|off> Define nested struct fields and struct array elements as pointers (outline only). off
--auto-pager, --pager <on|off> on: use a pager when the output is taller than the terminal. off: never. on
-o, --outline Output nested structs as separate type definitions (outline mode). outline mode
-i, --inline Output nested structs inline (inline mode). Cannot be used with --outline. false

Arguments

  • <root-name>: Used as the name of the root struct in the output. Automatically converted to camel case.
  • <json-file>: File containing JSON data. Can be read even if the extension is not .json.
  • <json-string>: Direct JSON string input.
  • <null-type-name>: The type name used to replace null type from json.

Environments

Environment Description Default
KIRKE_DEFAULT_ROOT_NAME Specified default used <root-name>. AutoGenerated
KIRKE_DEFAULT_NULL_AS Specified default used <null-type-name>. any
KIRKE_DEFAULT_OUTPUT_MODE Output mode used without --inline and --outline: inline, or outline for any other value. outline
KIRKE_DEFAULT_POINTER_STRUCT_MODE Default of --pointer-struct. Only on and off are valid; other values are ignored. off
KIRKE_DEFAULT_AUTO_PAGER_MODE Default of --auto-pager. Only on and off are valid; other values are ignored. on

Types

Every JSON value is typed so that json.Unmarshal decodes it into the generated struct and json.Marshal encodes it back with the same value (numbers by value, see below), except where noted.

JSON value Go type
string, true/false string, bool
null <null-type-name> (any)
object a struct (a pointer with --pointer-struct on, outline only)
integer that fits in int64 (42, -0) int
larger integer (9223372036854775808) *big.Int
fraction or exponent that float64 holds as written (1.5, 1.0, 0.1, 1e3, 1.2e-4) float64
fraction or exponent that float64 changes (3.141592653589793238462643383279, 1e400, 1e-400, 4e-324) json.Number (keeps the literal)
array a slice of one type for all its elements (below)

"float64 holds as written" means that the shortest decimal form of its float64 value, which json.Marshal writes, has the value of the JSON literal (1.0 is written 1, 0.1 stays 0.1). The generated code uses json.Number and json.RawMessage (package encoding/json) and *big.Int (package math/big) without the import declarations.

Arrays

An array is typed from all of its elements, whatever their order, in both output modes:

  • Numbers: the first of int, *big.Int (all integers), float64 (all held as written), json.Number that holds every one of them: [1, 2] is []int, [1, 2.5] is []float64, [1, 9223372036854775808] is []*big.Int, [0.1, 1e-400] is []json.Number.
  • Strings: []string; booleans: []bool.
  • Objects: one struct with the keys of all the objects, in the order they first appear. A key that several objects have is typed from all of its values, as array elements are: [{"v": 1}, {"v": 2.5}, {"w": "a"}] is a slice of struct { V float64; W string }.
  • Arrays: the elements of all of them: [[1, 2], [3, 4.5]] is [][]float64.
  • null among them makes the element a pointer: [1, null] is []*int, ["a", null] is []*string, [{...}, null] is a slice of struct pointers; *big.Int and slices already are nil for null. The same holds for a key of merged objects: [{"a": 1}, {"a": null}] gives A *int.
  • Elements of different kinds ([1, "a", true], [{...}, 1]): []interface{}, or []json.RawMessage when one of them is, or holds at any depth, a number float64 changes (interface{} would decode it into a float64).
  • Only null, or no element: []<null-type-name>.

What is kept

  • The value of every number: 1e3 comes back as 1000 and 1.0 as 1 (float64, int), the same values. json.Number and json.RawMessage keep the literal as written. kirke does not keep how a number was written in other types.
  • A key that only some objects of an array have decodes into its zero value for the others, and json.Marshal writes that zero value: the generated struct does not tell a missing key from its zero value (nor from null, unless the type is a pointer).
  • --null-as with a type that cannot hold null (string, int) decodes null to its zero value.
  • int assumes a 64-bit int: on a 32-bit platform (GOARCH=386, arm) an integer beyond int32 does not decode into it; use int64 there.

Names

  • A field is named after its key in camel case (user_id is UserId). A key that does not give a Go identifier keeps its letters, digits and underscores, each run starting with a capital letter, after an X when it would start with a digit or be empty ($set is Set, my-key is MyKey, 1st is X1st, _ is X); a name that would not be exported, which encoding/json ignores, also starts with X (名前 is X名前); when an earlier key of the object gives the same name, a number is appended (a_b and aB are AB and AB2). The keys whose camel case already is a name are named first, so a converted key never takes their name ({"_": 1, "x": 2} gives X2 and X). The JSON tag always has the key.
  • A key that appears more than once in one object is one field, typed from all of its values.
  • The empty key "" gives an embedded field, as before: encoding/json matches no struct field with it, so its value is not decoded.
  • A key that encoding/json cannot match with a struct tag (one with a quote, a backslash, a comma, an apostrophe, a control character or a symbol such as an emoji) is an error that names the key, rather than a field that would stay empty. The key - is tagged json:"-,".
  • Outline: a struct is named after its key (an array's element struct after the key and Item). When that name is already used in the output, by the root struct or by another struct, a number is appended (Meta2); a struct with the same name and the same fields is the same type. The structs that versions before arrays were typed from all elements generated (from an array's first element when it is not null) are named first, so their names do not change.
  • Inline: an empty object is struct{}.

Examples

Example 1: Direct JSON string input

Convert a JSON string to a struct without using a pager and with a specified root name:

kirke -j '{"key": "value"}' --pager off --name MyStruct

Example 2: JSON from a file

Convert JSON data from a specified file to a struct with a specified root name:

kirke -f ./path/to/example.json --name MyExample

Example 3: JSON input from a pipe

Read JSON data from a pipe, use pointers for nested fields, and output as a struct:

echo '{"key": "value"}' | kirke --pointer on

Example 4: JSON input from a pipe force.

Read JSON data from a pipe, use pointers for nested fields, and output as a struct (-j option is ignored.):

echo '{"key": "value"}' | kirke --pipe --pointer on -j '{"key2": "value2"}'

Example 5: JSON null as *string in GO struct.

Read JSON data from a pipe and use *string as the type of the null values:

echo '{"key": "value", "obj": {"address": null} }' | kirke -a "*string"

Author

Copyright (c) 2025 Hiroshi IKEGAMI

Licence

MIT License

About

Yet another JSON to Go Struct Converter

Topics

generatorgogofmtjsonstruct

Resources

Readme
MIT license
Activity

Stars

11 stars

Watchers

1 watching

Forks

0 forks
Report repository

Releases

Packages

Used by

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.