{"id":41408089,"url":"https://github.com/mennanov/fieldmask-utils","last_synced_at":"2026-01-23T13:28:46.366Z","repository":{"id":34408107,"uuid":"138357427","full_name":"mennanov/fieldmask-utils","owner":"mennanov","description":"Protobuf Field Mask Go utils","archived":false,"fork":false,"pushed_at":"2026-01-22T06:21:12.000Z","size":115,"stargazers_count":255,"open_issues_count":1,"forks_count":29,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-01-22T20:41:14.863Z","etag":null,"topics":["copy","fieldmask","golang","protobuf","struct"],"latest_commit_sha":null,"homepage":null,"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/mennanov.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-06-22T23:51:03.000Z","updated_at":"2026-01-22T06:10:23.000Z","dependencies_parsed_at":"2024-01-19T02:11:11.096Z","dependency_job_id":"22492731-cf97-46e6-a350-d1df33f3d424","html_url":"https://github.com/mennanov/fieldmask-utils","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/mennanov/fieldmask-utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Ffieldmask-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Ffieldmask-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Ffieldmask-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Ffieldmask-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mennanov","download_url":"https://codeload.github.com/mennanov/fieldmask-utils/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Ffieldmask-utils/sbom","scorecard":{"id":636562,"data":{"date":"2025-08-11","repo":{"name":"github.com/mennanov/fieldmask-utils","commit":"50a32d485ca739d547d4d9462d30caed294318e5"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":2,"reason":"Found 7/29 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/coverage.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/coverage.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/coverage.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/coverage.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/coverage.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/coverage.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linter.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/linter.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linter.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/linter.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/mennanov/fieldmask-utils/tests.yml/master?enable=pin","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/coverage.yml:1","Warn: no topLevel permission defined: .github/workflows/linter.yml:1","Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 13 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T09:20:43.955Z","repository_id":34408107,"created_at":"2025-08-21T09:20:43.955Z","updated_at":"2025-08-21T09:20:43.955Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28693324,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T11:01:27.039Z","status":"ssl_error","status_checked_at":"2026-01-23T11:00:26.909Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["copy","fieldmask","golang","protobuf","struct"],"created_at":"2026-01-23T13:28:45.761Z","updated_at":"2026-01-23T13:28:46.361Z","avatar_url":"https://github.com/mennanov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Protobuf Field Mask utils for Go\n\n[![Tests](https://github.com/mennanov/fieldmask-utils/actions/workflows/tests.yml/badge.svg)](https://github.com/mennanov/fieldmask-utils/actions/workflows/tests.yml)\n[![Coverage](https://codecov.io/gh/mennanov/fieldmask-utils/branch/master/graph/badge.svg?token=O7HtNMO6Ra)](https://codecov.io/gh/mennanov/fieldmask-utils)\n\nFeatures:\n\n* Copy from any Go struct to any compatible Go struct with a field mask applied\n* Copy from any Go struct to a `map[string]interface{}` with a field mask applied\n* Extensible masks (e.g. inverse mask: copy all except those mentioned, etc.)\n* Supports [Protobuf Any](https://developers.google.com/protocol-buffers/docs/proto3#any) message types.\n\nIf you're looking for a simple FieldMask library to work with protobuf messages only (not arbitrary structs) consider this tiny repo: [https://github.com/mennanov/fmutils](https://github.com/mennanov/fmutils)\n\n### Examples\n\nCopy from a protobuf message to a protobuf message:\n\n```proto\n// testproto/test.proto\n\nmessage UpdateUserRequest {\n    User user = 1;\n    google.protobuf.FieldMask field_mask = 2;\n}\n```\n\n```go\npackage main\n\nimport fieldmask_utils \"github.com/mennanov/fieldmask-utils\"\n\n// A function that maps field mask field names to the names used in Go structs.\n// It has to be implemented according to your needs.\n// Scroll down for a reference on how to apply field masks to your gRPC services.\nfunc naming(s string) string {\n\tif s == \"foo\" {\n\t\treturn \"Foo\"\n\t}\n\treturn s\n}\n\nfunc main () {\n\tvar request UpdateUserRequest\n\tuserDst := \u0026testproto.User{} // a struct to copy to\n\tmask, _ := fieldmask_utils.MaskFromPaths(request.FieldMask.Paths, naming)\n\tfieldmask_utils.StructToStruct(mask, request.User, userDst)\n\t// Only the fields mentioned in the field mask will be copied to userDst, other fields are left intact\n}\n```\n\nCopy from a protobuf message to a `map[string]interface{}`:\n\n```go\npackage main\n\nimport fieldmask_utils \"github.com/mennanov/fieldmask-utils\"\n\nfunc main() {\n\tvar request UpdateUserRequest\n\tuserDst := make(map[string]interface{}) // a map to copy to\n\tmask, _ := fieldmask_utils.MaskFromProtoFieldMask(request.FieldMask, naming)\n\terr := fieldmask_utils.StructToMap(mask, request.User, userDst)\n\t// Only the fields mentioned in the field mask will be copied to userDst, other fields are left intact\n}\n```\n\nCopy with an inverse mask:\n\n```go\npackage main\n\nimport fieldmask_utils \"github.com/mennanov/fieldmask-utils\"\n\nfunc main() {\n\tvar request UpdateUserRequest\n\tuserDst := \u0026testproto.User{} // a struct to copy to\n\tmask := fieldmask_utils.MaskInverse{\"Id\": nil, \"Friends\": fieldmask_utils.MaskInverse{\"Username\": nil}}\n\tfieldmask_utils.StructToStruct(mask, request.User, userDst)\n\t// Only the fields that are not mentioned in the field mask will be copied to userDst, other fields are left intact.\n}\n```\n\n#### Naming function\n\nFor developers that are looking for a mechanism to apply a mask field in their update endpoints using gRPC services,\nthere are multiple options for the naming function described above:\n\n- Using the `CamelCase` function provided in\n  the [original protobuf repository](https://github.com/golang/protobuf/blob/master/protoc-gen-go/generator/generator.go#L2648).\n  This repository has been deprecated and it will potentially trigger lint errors.\n    - You can copy-paste the `CamelCase` function to your own project or,\n    - You can use an [Open Source alternative](https://github.com/gojaguar/jaguar) that provides the same functionality,\n      already took care of [copying the code](https://github.com/gojaguar/jaguar/blob/main/strings/pascal_case.go), and also added tests.\n\n```go\nfunc main() {\n    mask := \u0026fieldmaskpb.FieldMask{Paths: []string{\"username\"}}\n    mask.Normalize()\n    req := \u0026UpdateUserRequest{\n        User: \u0026User{\n            Id:       1234,\n            Username: \"Test\",\n        },\n    }\n    if !mask.IsValid(req) {\n        return\n    }\n    protoMask, err := fieldmask_utils.MaskFromProtoFieldMask(mask, strings.PascalCase)\n    if err != nil {\n        return\n    }\n    m := make(map[string]any)\n    err = fieldmask_utils.StructToMap(protoMask, req, m)\n\tif err != nil {\n\t\treturn\n    }\n\tfmt.Println(\"Resulting map:\", m)\n}\n```\n\nThis will result in a map that contains the fields that need to be updated with their respective values.\n\n#### Converter hooks\n\nWhen trying to assign a source field to a destination using different types, one can use the Option `WithConverterHook`.\nAll provided converter functions will be tried in order until src is assignable to dst or an error occured.\n\nBelow you will find an example of how to convert from string to int64 when applying a mask by specifying such converter:\n\n```go\ntype A struct {\n    Field1 string\n}\ntype B struct {\n    Field1 int64\n}\n\nfunc main() {\n\tsrc := \u0026A{\n\t\tField1: \"   42   \",\n\t}\n\tdst := \u0026B{}\n\tmask := fieldmask_utils.MaskFromString(\"Field1\")\n\n    err = fieldmask_utils.StructToStruct(mask, src, dst,\n\t\tfieldmask_utils.WithConverterHook(func(src, dst *reflect.Value) (interface{}, error) {\n\t\t\tdata := src.Interface()\n\n            // only care for this conversion\n\t\t\tif src.Kind() != reflect.String ||\n\t\t\t\tdst.Kind() != reflect.Int64 {\n\t\t\t\treturn data, nil\n\t\t\t}\n\n            // cast it\n\t\t\traw, ok := data.(string)\n\t\t\tif !ok {\n\t\t\t\treturn data, nil\n\t\t\t}\n\n            // parse it\n\t\t\treturn strconv.ParseInt(strings.TrimSpace(raw), 10, 64)\n\t\t}))\n\n\tfmt.Println(\"src:\", src)\n\tfmt.Println(\"dst:\", dst)\n}\n```\n\n### Limitations\n\n1.  Larger scope field masks have no effect and are not considered invalid:\n\n    field mask strings `\"a\", \"a.b\", \"a.b.c\"` will result in a mask `a{b{c}}`, which is the same as `\"a.b.c\"`.\n\n2.  Masks inside a protobuf `Map` are not supported.\n3.  When copying from a struct to struct the destination struct must have the same fields (or a subset)\n    as the source struct. Either of source or destination fields can be a pointer as long as it is a pointer to\n    the type of the corresponding field.\n4. `oneof` fields are represented differently in `fieldmaskpb.FieldMask` compared to `fieldmask_util.Mask`. In\n    [FieldMask](https://pkg.go.dev/google.golang.org/protobuf/types/known/fieldmaskpb#:~:text=%23%20Field%20Masks%20and%20Oneof%20Fields)\n    the fields are represented using their property name, in this library they are prefixed with the `oneof` name\n    matching how Go generated code is laid out. This can lead to issues when converting between the two, for example\n    when using `MaskFromPaths` or `MaskFromProtoFieldMask`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmennanov%2Ffieldmask-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmennanov%2Ffieldmask-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmennanov%2Ffieldmask-utils/lists"}