Welcome to xMapper
, where your Go structs gain superpowers! 🚀 Ever tired of manually copying fields from one struct to another? Forget about the hassle! xMapper
automates the mapping of structs and dynamically transforms data as it flows from source to destination. It's not just about mapping; it's about transforming data effortlessly with power and style!
-
Automatic Struct Mapping: Automate the boring stuff! Map fields between structs without writing boilerplate code.
-
Single Value Validation and Transforming: Validate and transform individual values to ensure they meet specific criteria or format requirements before they are used.
-
Dynamic Data Transformation: Apply transformations to your data dynamically as it's being mapped. Upper case, add suffixes, manipulate data on the go!
-
Extensible and Customizable: Easily extend
xMapper
by adding your own custom transformation functions. -
Data Validation: Ensure data integrity with custom validators that check data before transformation.
-
Default Validation Rules: Check input using built-in validators for common requirements like email format, length, or specific starting/ending characters without writing custom code.
-
Error Handling: Robust error handling to let you know exactly what went wrong during the mapping process.
xMapper also provides powerful and flexible capabilities to automatically handle various types of conversions in Go. It can seamlessly map data between different structures, including:
- JSON to Struct: Convert JSON strings into Go structs.
- Struct to JSON: Convert Go structs into JSON strings.
- JSON to Slice: Convert JSON strings into slices.
- Slice to JSON: Convert slices into JSON strings.
Here’s how to get started with xMapper
:
To start using xMapper
in your Go project, simply use the go get
command to retrieve the package:
go get github.com/dev3mike/go-xmapper
Ensure your environment is set up with Go modules (Go 1.11+ required), and this command will manage everything for you, fetching the latest version of xMapper
and adding it to your project's dependencies.
Validate a single struct without mapping
If you don't need any mapping, you can directly validate the struct. You can also use transformers to ensure your data is clean. Exciting, isn't it?
type User struct {
Email string `json:"email" validators:"email" transformers:"trim,lowercase"`
}
user := User{Email: "[email protected]"}
// Call ValidateStruct to check validation.
err := xmapper.ValidateStruct(&user)
// Check error type for validation errors
if errors.Is(err, xmapper.ErrValidation){
// Return bad request or ...
}
Transformers
- Define Your Transformers: Create functions that match the
TransformerFunc
signature:
func toUpperCase(input interface{}) interface{} {
str, ok := input.(string)
if ok {
return strings.ToUpper(str)
}
return input
}
- Register Your Transformers: Before you map your structs, make sure to register your transformers:
xmapper.RegisterTransformer("toUpperCase", toUpperCase)
- Map Your Structs: Now let the magic happen:
type Source struct {
Name string `json:"name" transformers:"toUpperCase"`
}
type Destination struct {
Name string `json:"name"`
}
src := Source{Name: "frodo"}
dest := Destination{}
err := xmapper.MapStructs(&src, &dest)
if err != nil {
fmt.Println("Oops! Something went wrong:", err)
}
Map slice of structs You can use MapSliceOfStructs to map slice of structs
xmapper.MapSliceOfStructs(&src, &dest)
- Validate/Transform single values:
email := "[email protected]"
transformedEmail, err := xmapper.ValidateSingleField(value, "validators:'isEmail' transformers:'toUpperCase'")
if err != nil {
// Check error type for validation errors
if errors.Is(err, xmapper.ErrValidation){
// Return bad request or ...
}
fmt.Println("Oops! Something went wrong:", err)
return
}
fmt.Printf("Hello %s", transformedEmail.(string))
type ProfileDto struct {
Email string `json:"email" validators:"isEmail" transformers:"toUpperCase"`
Age int `json:"age"`
}
jsonStr := `{"email":"[email protected]","age":30}`
var profile ProfileDto
err := xmapper.MapJsonStruct(jsonStr, &profile)
if err != nil {
// Check error type for validation errors
if errors.Is(err, xmapper.ErrValidation){
// Return bad request or ...
}
t.Errorf("MapJsonStruct failed: %s", err)
}
Want to ensure your transformers are set up correctly? Here’s how to handle errors:
err := xmapper.MapStructs(&src, &dest)
if err != nil {
// Check error type for validation errors
if errors.Is(err, xmapper.ErrValidation){
// Return bad request or ...
}
fmt.Println("Failed to map structs:", err)
}
You can use these default transformers without a need of registering them.
Transformer | Description |
---|---|
uppercase |
Converts text to uppercase letters |
lowercase |
Converts text to lowercase letters |
trim |
Removes whitespace from both sides of the text |
trimLeft |
Removes whitespace from the left side of the text |
trimRight |
Removes whitespace from the right side of the text |
base64Encode |
Encodes text to Base64 format |
base64Decode |
Decodes Base64 text to original format |
urlEncode |
Encodes text to be URL-friendly |
urlDecode |
Decodes URL-encoded text to original format |
Example Code:
type Source struct {
Greeting string `json:"greeting" transformers:"uppercase,trim"`
}
xMapper
allows you to apply multiple transformations to a single field in sequence, which can be extremely powerful for complex data manipulation. This section guides you through setting up and using multiple transformers on a single struct field.
First, define each transformer function. Each function should match the TransformerFunc
signature. Here are examples of three simple transformers:
// Converts a string to uppercase
func toUpperCase(input interface{}) interface{} {
str, ok := input.(string)
if ok {
return strings.ToUpper(str)
}
return input
}
// Adds an exclamation mark at the end of a string
func addExclamation(input interface{}) interface{} {
str, ok := input.(string)
if ok {
return str + "!"
}
return input
}
// Repeats the string twice, separated by a space
func repeatTwice(input interface{}) interface{} {
str, ok := input.(string)
if ok {
return str + " " + str
}
return input
}
Register each transformer with xMapper
before you attempt to map your structs:
func init() {
xmapper.RegisterTransformer("toUpperCase", toUpperCase)
xmapper.RegisterTransformer("addExclamation", addExclamation)
xmapper.RegisterTransformer("repeatTwice", repeatTwice)
}
Define your source and destination structs. Use the transformer
tag to specify multiple transformers separated by commas. The transformers will be applied in the order they are listed:
type Source struct {
Greeting string `json:"greeting" transformers:"toUpperCase,addExclamation,repeatTwice"`
}
type Destination struct {
Greeting string `json:"greeting"`
}
func main() {
src := Source{Greeting: "hello"}
dest := Destination{}
if err := xmapper.MapStructs(&src, &dest); err != nil {
fmt.Println("Error mapping structs:", err)
} else {
fmt.Println("Mapped Greeting:", dest.Greeting)
// Output should be: "HELLO! HELLO!"
}
}
Validators in xMapper
ensure your data meets specific criteria before it's transformed and mapped to the destination struct. Validators can prevent invalid data from being processed and provide descriptive error messages if data validation fails.
You can use built-in validator as follows:
Validator Name | Description |
---|---|
required |
Checks if the input is not empty. |
email |
Validates that the input is a valid email address. |
phone |
Checks if the input is a valid international phone number. |
strongPassword |
Requires at least 8 characters, including upper, lower, digit, and special character. |
date |
Validates that the input matches the YYYY-MM-DD date format. |
time |
Validates that the input matches the HH:MM:SS time format. |
datetime |
Validates date and time with timezone in YYYY-MM-DD HH:MM:SS format. |
url |
Checks if the input is a valid URL. |
ip |
Validates that the input is a valid IP address. |
minLength |
Checks if the input has at least a specified minimum length. |
maxLength |
Ensures the input does not exceed a specified maximum length. |
gt |
Validates that a number is greater than a specified value. |
lt |
Validates that a number is less than a specified value. |
gte |
Checks if a number is greater than or equal to a specified value. |
lte |
Checks if a number is less than or equal to a specified value. |
range |
Validates that a number falls within a specified range. |
enum |
Checks if the input matches one of a list of predefined values. |
boolean |
Validates that the input is a boolean value. |
contains |
Checks if the input contains one of the specified substrings. |
notContains |
Ensures the input does not contain any of the specified substrings. |
startsWith |
Validates that the input starts with a specified substring. |
endsWith |
Checks if the input ends with a specified substring. |
How to use built-in validators:
type User struct {
Name string `json:"name" validators:"required,minLength:3,maxLength:100"`
Email string `json:"email" validators:"email,maxLength:255"`
Username string `json:"username" validators:"minLength:6,maxLength:27"`
Password string `json:"password" validators:"strongPassword"`
Type string `json:"type" validators:"enum:buyer-seller"`
}
If you need a custom validation logic, then you can register and use your own validator.
First, define each validator function. Each function must match the ValidatorFunc
signature, which typically returns true
if the validation passes and false
otherwise:
// Ensures the input string is not empty
// The second argument is always required.
// It's used to receive specific settings for the validation.
// For instance, for `minLength:10`,
// the number `10` is the argument that sets the minimum length.
func isNotEmpty(input interface{}, _ string) bool {
str, ok := input.(string)
return ok && str != ""
}
// Checks that the input string does not contain spaces
func doesNotContainSpaces(input interface{}, _ string) bool {
str, ok := input.(string)
return ok && !strings.Contains(str, " ")
}
// Checks if the input string's length is at least the specified minimum
func MinLengthValidator(input interface{}, length string) error {
str, ok := input.(string)
if !ok {
return fmt.Errorf("failed to map the input to a string")
}
minLength, err := strconv.Atoi(length)
if err != nil {
return fmt.Errorf("failed to convert length to integer")
}
if len(str) < minLength {
return fmt.Errorf("input does not meet the minimum length requirement, minimum length is %s", length)
}
return nil
}
Register each validator with xMapper
just like you register transformers. This registration links the validator name with its corresponding function:
func init() {
xmapper.RegisterValidator("isNotEmpty", isNotEmpty)
xmapper.RegisterValidator("doesNotContainSpaces", doesNotContainSpaces)
xmapper.RegisterValidator("customMinLength", MinLengthValidator)
}
When defining your structs, use the validator
tag to assign validators to struct fields. Multiple validators can be applied to a single field and are separated by commas. Validators will be executed in the order they are listed:
type Person struct {
FirstName string `json:"firstName" validators:"isNotEmpty,doesNotContainSpaces"`
LastName string `json:"lastName" validators:"isNotEmpty,customMinLength:4"`
}
type Destination struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
}
When mapping your structs, handle any validation errors that might arise. If a validation fails, xMapper
will return an error indicating which field and validator failed:
src := Person{FirstName: "John", LastName: "Doe"}
dest := Destination{}
err := xmapper.MapStructs(&src, &dest)
if err != nil {
fmt.Println("Validation error:", err)
return
}
fmt.Println("Data successfully validated and mapped:", dest)
Got a cool idea for a new feature? Found a bug? We love contributions!
- Fork the repo.
- Create a new branch (
git checkout -b cool-new-feature
). - Commit your changes (
git commit -am 'Add some feature'
). - Push to the branch (
git push origin cool-new-feature
). - Create a new Pull Request.
Distributed under the MIT License. See LICENSE
for more information.
Feel the power of easy struct transformations and focus on what really matters in your Go applications. Try xMapper
today and say goodbye to boilerplate code! 🎉