{"id":26862196,"url":"https://github.com/mashingan/smapping","last_synced_at":"2025-05-06T20:26:43.078Z","repository":{"id":33956828,"uuid":"139261109","full_name":"mashingan/smapping","owner":"mashingan","description":"Golang struct generic mapping","archived":false,"fork":false,"pushed_at":"2022-09-25T13:19:02.000Z","size":116,"stargazers_count":94,"open_issues_count":0,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-31T02:35:33.785Z","etag":null,"topics":["go","golang","golang-library","reflection","structs"],"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/mashingan.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":"2018-06-30T15:50:29.000Z","updated_at":"2025-03-21T16:29:00.000Z","dependencies_parsed_at":"2022-08-07T23:30:58.742Z","dependency_job_id":null,"html_url":"https://github.com/mashingan/smapping","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fsmapping","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fsmapping/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fsmapping/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fsmapping/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mashingan","download_url":"https://codeload.github.com/mashingan/smapping/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252763279,"owners_count":21800457,"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":["go","golang","golang-library","reflection","structs"],"created_at":"2025-03-31T02:35:37.344Z","updated_at":"2025-05-06T20:26:43.022Z","avatar_url":"https://github.com/mashingan.png","language":"Go","readme":"[![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=plastic)](./LICENSE)\n[![CircleCI](https://circleci.com/gh/mashingan/smapping.svg?style=svg)](https://circleci.com/gh/mashingan/smapping)\n[![GoDoc](https://godoc.org/github.com/mashingan/smapping?status.svg)](https://godoc.org/github.com/mashingan/smapping)\n[![Go Report Card](https://goreportcard.com/badge/github.com/mashingan/smapping)](https://goreportcard.com/report/github.com/mashingan/smapping)\n\n# smapping\nGolang structs generic mapping.\n\n### Version Limit\nTo support nesting object conversion, the lowest Golang version supported is `1.12.0`.  \nTo support `smapping.SQLScan`, the lowest Golang version supported is `1.13.0`.\n\n# Table of Contents\n1. [Motivation At Glimpse](#at-glimpse).\n2. [Motivation Length](#motivation).\n3. [Install](#install).\n4. [Examples](#examples).\n\t* [Basic usage examples](#basic-usage-examples)\n\t* [Nested object example](#nested-object-example)\n\t* [SQLScan usage example](#sqlscan-usage-example)\n\t* [Omit fields example](#omit-fields-example)\n5. [License](#license).\n\n# At Glimpse\n## What?\nA library to provide a mapped structure generically/dynamically.\n\n## Who?\nAnyone who has to work with large structure.\n\n## Why?\nScalability and Flexibility.\n\n## When?\nAt the runtime.\n\n## Where?\nIn users code.\n\n## How?\nBy converting into `smapping.Mapped` which alias for `map[string]interface{}`,\nusers can iterate the struct arbitarily with `reflect` package.\n\n# Motivation\nWorking with between ``struct``, and ``json`` with **Golang** has various\ndegree of difficulty.\nThe thing that makes difficult is that sometimes we get arbitrary ``json``\nor have to make json with arbitrary fields. Sometime we also need to have\na different field names, extracting specific fields, working with same\nstructure with different domain fields name etc.\n\nIn order to answer those flexibility, we map the object struct to the\nmore general data structure as table/map.\n\nTable/Map is the data structure which ubiquitous after list,\nwhich in turn table/map can be represented as\nlist of pair values (In Golang we can't have it because there's no tuple\ndata type, tuple is limited as return values).\n\nObject can be represented as table/map dynamically just like in\nJavaScript/EcmaScript which object is behaving like table and in\nLua with its metatable. By some extent we can represent the JSON\nas table too.\n\nIn this library, we provide the mechanism to smoothly map the object\nrepresentation back-and-forth without having the boilerplate of\ntype-checking one by one by hand. Type-checking by hand is certainly\n*seems* easier when the domain set is small, but it soon becomes\nunbearable as the structure and/or architecure dynamically changed\nbecause of newer insight and information. Hence in [`Who section`](#who)\nmentioned this library is for anyone who has to work with large\ndomain set.\n\nExcept for type `smapping.Mapped` as alias, we don't provide others\ntype struct currently as each operation doesn't need to keep the\ninternal state so each operation is transparent and *almost* functional\n(*almost functional* because we modify the struct fields values instead of\nreturning the new struct itself, but this is only trade-off because Golang\ndoesn't have type-parameter which known as generic).\n\nSince `v0.1.10`, we added the [`MapEncoder`](smapping.go#L21) and\n[`MapDecoder`](smapping.go#L27) interfaces for users to have custom conversion\nfor custom and self-defined struct.\n\n## Install\n```\ngo get github.com/mashingan/smapping\n```\n\n## Examples\n\n### Basic usage examples\nBelow example are basic representation how we can work with `smapping`.\nSeveral examples are converged into single runnable example for the ease\nof reusing the same structure definition and its various tags.\nRefer this example to get a glimpse of how to do things. Afterward,\nusers can creatively use to accomplish what they're wanting to do\nwith the provided flexibility.\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/mashingan/smapping\"\n)\n\ntype Source struct {\n\tLabel   string `json:\"label\"`\n\tInfo    string `json:\"info\"`\n\tVersion int    `json:\"version\"`\n}\n\ntype Sink struct {\n\tLabel string\n\tInfo  string\n}\n\ntype HereticSink struct {\n\tNahLabel string `json:\"label\"`\n\tHahaInfo string `json:\"info\"`\n\tVersion  string `json:\"heretic_version\"`\n}\n\ntype DifferentOneField struct {\n\tName    string `json:\"name\"`\n\tLabel   string `json:\"label\"`\n\tCode    string `json:\"code\"`\n\tPrivate string `json:\"private\" api:\"internal\"`\n}\n\nfunc main() {\n\tsource := Source{\n\t\tLabel:   \"source\",\n\t\tInfo:    \"the origin\",\n\t\tVersion: 1,\n\t}\n\tfmt.Println(\"source:\", source)\n\tmapped := smapping.MapFields(source)\n\tfmt.Println(\"mapped:\", mapped)\n\tsink := Sink{}\n\terr := smapping.FillStruct(\u0026sink, mapped)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(\"sink:\", sink)\n\n\tmaptags := smapping.MapTags(source, \"json\")\n\tfmt.Println(\"maptags:\", maptags)\n\thereticsink := HereticSink{}\n\terr = smapping.FillStructByTags(\u0026hereticsink, maptags, \"json\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(\"heretic sink:\", hereticsink)\n\n\tfmt.Println(\"=============\")\n\trecvjson := []byte(`{\"name\": \"bella\", \"label\": \"balle\", \"code\": \"albel\", \"private\": \"allbe\"}`)\n\tdof := DifferentOneField{}\n\t_ = json.Unmarshal(recvjson, \u0026dof)\n\tfmt.Println(\"unmarshaled struct:\", dof)\n\n\tmarshaljson, _ := json.Marshal(dof)\n\tfmt.Println(\"marshal back:\", string(marshaljson))\n\n\t// What we want actually \"internal\" instead of \"private\" field\n\t// we use the api tags on to make the json\n\tapijson, _ := json.Marshal(smapping.MapTagsWithDefault(dof, \"api\", \"json\"))\n\tfmt.Println(\"api marshal:\", string(apijson))\n\n\tfmt.Println(\"=============\")\n\t// This time is the reverse, we receive \"internal\" field when\n\t// we need to receive \"private\" field to match our json tag field\n\trespjson := []byte(`{\"name\": \"bella\", \"label\": \"balle\", \"code\": \"albel\", \"internal\": \"allbe\"}`)\n\trespdof := DifferentOneField{}\n\t_ = json.Unmarshal(respjson, \u0026respdof)\n\tfmt.Println(\"unmarshal resp:\", respdof)\n\n\t// to get that, we should put convert the json to Mapped first\n\tjsonmapped := smapping.Mapped{}\n\t_ = json.Unmarshal(respjson, \u0026jsonmapped)\n\t// now we fill our struct respdof\n\t_ = smapping.FillStructByTags(\u0026respdof, jsonmapped, \"api\")\n\tfmt.Println(\"full resp:\", respdof)\n\treturnback, _ := json.Marshal(respdof)\n\tfmt.Println(\"marshal resp back:\", string(returnback))\n\t// first we unmarshal respdof, we didn't get the \"private\" field\n\t// but after our mapping, we get \"internal\" field value and\n\t// simply marshaling back to `returnback`\n}\n```\n\n### Nested object example\nThis example illustrates how we map back-and-forth even with deep\nnested object structure. The ability to map nested objects is to\ncreatively change its representation whether to flatten all tagged\nfield name even though the inner struct representation is nested.\nRegardless of the usage (`whether to flatten the representation`) or\njust simply fetching and remapping into different domain name set,\nthe ability to map the nested object is necessary.\n\n```go\n\ntype RefLevel3 struct {\n\tWhat string `json:\"finally\"`\n}\ntype Level2 struct {\n\t*RefLevel3 `json:\"ref_level3\"`\n}\ntype Level1 struct {\n\tLevel2 `json:\"level2\"`\n}\ntype TopLayer struct {\n\tLevel1 `json:\"level1\"`\n}\ntype MadNest struct {\n\tTopLayer `json:\"top\"`\n}\n\nvar madnestStruct MadNest = MadNest{\n\tTopLayer: TopLayer{\n\t\tLevel1: Level1{\n\t\t\tLevel2: Level2{\n\t\t\t\tRefLevel3: \u0026RefLevel3{\n\t\t\t\t\tWhat: \"matryoska\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n}\n\nfunc main() {\n\t// since we're targeting the same MadNest, both of functions will yield\n\t// same result hence this unified example/test.\n\tvar madnestObj MadNest\n\tvar err error\n\ttestByTags := true\n\tif testByTags {\n\t\tmadnestMap := smapping.MapTags(madnestStruct, \"json\")\n\t\terr = smapping.FillStructByTags(\u0026madnestObj, madnestMap, \"json\")\n\t} else {\n\t\tmadnestMap := smapping.MapFields(madnestStruct)\n\t\terr = smapping.FillStruct(\u0026madnestObj)\n\t}\n\tif err != nil {\n\t\tfmt.Printf(\"%s\", err.Error())\n\t\treturn\n\t}\n\t// the result should yield as intented value.\n\tif madnestObj.TopLayer.Level1.Level2.RefLevel3.What != \"matryoska\" {\n\t\tfmt.Printf(\"Error: expected \\\"matroska\\\" got \\\"%s\\\"\", madnestObj.Level1.Level2.RefLevel3.What)\n\t}\n}\n```\n\n### SQLScan usage example\nThis example, we're using `sqlite3` as the database, we add a convenience\nfeature for any struct/type that implements `Scan` method as `smapping.SQLScanner`.\nKeep in mind this is quite different with `sql.Scanner` that's also requiring\nthe type/struct to implement `Scan` method. The difference here, `smapping.SQLScanner`\nreceiving variable arguments of `interface{}` as values' placeholder while `sql.Scanner`\nis only receive a single `interface{}` argument as source. `smapping.SQLScan` is\nworking for `Scan` literally after we've gotten the `*sql.Row` or `*sql.Rows`.\n\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/mashingan/smapping\"\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\ntype book struct {\n\tAuthor author `json:\"author\"`\n}\n\ntype author struct {\n\tNum  int            `json:\"num\"`\n\tID   sql.NullString `json:\"id\"`\n\tName sql.NullString `json:\"name\"`\n}\n\nfunc (a author) MarshalJSON() ([]byte, error) {\n\tmapres := map[string]interface{}{}\n\tif !a.ID.Valid {\n\t\t//if a.ID == nil || !a.ID.Valid {\n\t\tmapres[\"id\"] = nil\n\t} else {\n\t\tmapres[\"id\"] = a.ID.String\n\t}\n\t//if a.Name == nil || !a.Name.Valid {\n\tif !a.Name.Valid {\n\t\tmapres[\"name\"] = nil\n\t} else {\n\t\tmapres[\"name\"] = a.Name.String\n\t}\n\tmapres[\"num\"] = a.Num\n\treturn json.Marshal(mapres)\n}\n\nfunc getAuthor(db *sql.DB, id string) author {\n\tres := author{}\n\terr := db.QueryRow(\"select * from author where id = ?\", id).\n\t\tScan(\u0026res.Num, \u0026res.ID, \u0026res.Name)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn res\n}\n\nfunc getAuthor12(db *sql.DB, id string) author {\n\tresult := author{}\n\tfields := []string{\"num\", \"id\", \"name\"}\n\terr := smapping.SQLScan(\n\t\tdb.QueryRow(\"select * from author where id = ?\", id),\n\t\t\u0026result,\n\t\t\"json\",\n\t\tfields...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn result\n}\n\nfunc getAuthor13(db *sql.DB, id string) author {\n\tresult := author{}\n\tfields := []string{\"num\", \"name\"}\n\terr := smapping.SQLScan(\n\t\tdb.QueryRow(\"select num, name from author where id = ?\", id),\n\t\t\u0026result,\n\t\t\"json\",\n\t\tfields...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn result\n}\n\nfunc getAllAuthor(db *sql.DB) []author {\n\tresult := []author{}\n\trows, err := db.Query(\"select * from author\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfor rows.Next() {\n\t\tres := author{}\n\t\tif err := smapping.SQLScan(rows, \u0026res, \"json\"); err != nil {\n\t\t\tfmt.Println(\"error scan:\", err)\n\t\t\tbreak\n\t\t}\n\t\tresult = append(result, res)\n\t}\n\treturn result\n}\n\nfunc main() {\n\tdb, err := sql.Open(\"sqlite3\", \"./dummy.db\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer db.Close()\n\t_, err = db.Exec(`\ndrop table if exists author;\ncreate table author(num integer primary key autoincrement, id text, name text);\ninsert into author(id, name) values\n('id1', 'name1'),\n('this-nil', null);`)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t//auth1 := author{ID: \u0026sql.NullString{String: \"id1\"}}\n\tauth1 := author{ID: sql.NullString{String: \"id1\"}}\n\tauth1 = getAuthor(db, auth1.ID.String)\n\tfmt.Println(\"auth1:\", auth1)\n\tjsonbyte, _ := json.Marshal(auth1)\n\tfmt.Println(\"json auth1:\", string(jsonbyte))\n\tb1 := book{Author: auth1}\n\tfmt.Println(b1)\n\tjbook1, _ := json.Marshal(b1)\n\tfmt.Println(\"json book1:\", string(jbook1))\n\tauth2 := getAuthor(db, \"this-nil\")\n\tfmt.Println(\"auth2:\", auth2)\n\tjbyte, _ := json.Marshal(auth2)\n\tfmt.Println(\"json auth2:\", string(jbyte))\n\tb2 := book{Author: auth2}\n\tfmt.Println(\"book2:\", b2)\n\tjbook2, _ := json.Marshal(b2)\n\tfmt.Println(\"json book2:\", string(jbook2))\n\tfmt.Println(\"author12:\", getAuthor12(db, auth1.ID.String))\n\tfmt.Println(\"author13:\", getAuthor13(db, auth1.ID.String))\n\tfmt.Println(\"all author1:\", getAllAuthor(db))\n}\n\n```\n\n### Omit fields example\n\nOften we need to reuse the same object with exception a field or two. With smapping it's possible to generate\nmap with custom tag. However having different tag would be too much of manual work.  \nIn this example, we'll see how to exclude using the `delete` keyword.\n\n```go\npackage main\n\nimport (\n\t\"github.com/mashingan/smapping\"\n)\n\ntype Struct struct {\n\tField1       int    `json:\"field1\"`\n\tField2       string `json:\"field2\"`\n\tRequestOnly  string `json:\"input\"`\n\tResponseOnly string `jsoN:\"output\"`\n}\n\nfunc main() {\n\ts := Struct{\n\t\tField1:       5,\n\t\tField2:       \"555\",\n\t\tRequestOnly:  \"vanish later\",\n\t\tResponseOnly: \"still available\",\n\t}\n\n\tm := smapping.MapTags(s, \"json\")\n\t_, ok := m[\"input\"]\n\tif !ok {\n\t\tpanic(\"key 'input' should be still available\")\n\t}\n\tdelete(m, \"input\")\n\t_, ok = m[\"input\"]\n\tif ok {\n\t\tpanic(\"key 'input' should be not available\")\n\t}\n}\n```\n\n## LICENSE\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmashingan%2Fsmapping","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmashingan%2Fsmapping","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmashingan%2Fsmapping/lists"}