gopartial

package module
v0.0.0-...-ffee370 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2022 License: MIT Imports: 7 Imported by: 0

README

gopartial - Golang Partial Struct Update

This mini library provides a helper function to update go struct partially from incoming json data.

Why?

The challenge with golang is it is hard to take a dynamic data such as json and apply partial update to a golang struct due to the 'unknown' type/value that you might receive from a field in a json data.

The challenge becomes even harder when dealing with nullable data because if you use json null value as a way to determine whether a struct field should be updated or not, you'll lose ability to update the struct to null value.

A good practical example of this problem is when you want to implement HTTP PATCH Request.

Installation

$ go get github.com/roserocket/gopartial

Dependencies

$ go get github.com/guregu/null

Example

import (
    "time"
    "log"

    "github.com/roserocket/gopartial"
)

// User struct
type User struct {
    ID          string          `json:"id"`
    Name        string          `json:"name"`
    Age         *int            `json:"age"` // Can be null
    DeletedAt   *time.Time      `json:"deleted_at"` // Can be null
}

// Imagine you have a Web API that can partially update an existing User in database
func UpdateUserPartially(user *User, partialDataJSON json.RawMessage) (*User, error) {


    var partialData map[string]interface{}
    if err := json.Unmarshal(partialDataJSON, &partialData); err != nil {
        log.Fatal(err)
    }

    updatedFields, err := gopartial.PartialUpdate(user, partialData, "json", gopartial.SkipConditions, gopartial.Updaters)
    log.Println("Updated fields: ", updatedFields)

    return user, err
}

func main() {
    t := time.Now()
    // Existing user data
    user := &User{
        ID:         "1",
        Name:       "John",
        Age:        nil,
        DeletedAt:  &t,
    }

    log.Printf("Initial user object: %+v", user)

    // You want to update just name, age and deleted_at
    partialDataJSON := json.RawMessage(`{"name": "Johnson", "age": 21, "deleted_at": null}`)

    var err error
    user, err = UpdateUserPartially(user, partialDataJSON)
    if err != nil {
        log.Fatal(err)
        return
    }

    // Updated user data should now be:
    // User{
    //     ID:         "1",
    //     Name:       "Johnson",
    //     Age:        21,
    //     DeletedAt:  nil,
    // }
    log.Printf("Updated user object: %+v", user)
}

Methods

func PartialUpdate(dest interface{}, partial map[string]interface{}, tagName string, skipConditions []func(reflect.StructField) bool, updaters []func(reflect.Value, reflect.Value) bool) ([]string, error)
Argument Type Description
dest interface{} Destination struct (Must be a pointer to struct)
partial map[string]interface{} Partial data in the form of map[string]interface{}
tagName string The struct tag name that you'll be mapping the struct field to based on the json field name
skipConditions []func(reflect.StructField) bool Array of skip condition functions
updaters []func(reflect.Value, reflect.Value) bool Array of updater functions

This function can be easily extended if you have certain skip conditions while updating the struct. For example you want to skip all the struct field that has tagname props with value of readonly, then you can create a function as follow:

// SkipReadOnly skips all field that has tag readonly
func SkipReadOnly(field reflect.StructField) bool {
	props := strings.Split(field.Tag.Get("props"), ",")
	return utils.IndexOf(props, "readonly") >= 0
}

You can also extend this function to update a certain custom type within your application. Example:

type MyType string

// MyTypeUpdater update MyType
func MyTypeUpdater(fieldValue reflect.Value, v reflect.Value) bool {
	switch fieldValue.Interface().(type) {
	case MyType:
		// if its null value
		if !v.IsValid() {
			newValue := reflect.ValueOf(MyType{}})
			fieldValue.Set(newValue)
			return true
		}
		// only set if underlying type is a string
		if v.Kind() == reflect.String {
			newValue := reflect.ValueOf(MyType(v.String()))
			fieldValue.Set(newValue)
			return true
		}
	}

	return false
}
Why do we need updatedFields returned?

The idea is using the list of updated fields, you can dynamically build the sql query to update the record in the database.

Hint: use reflect.Type.FieldByName function to get the reflect.StructField and use reflect.StructField.Tag.Get("db") to get the db field name.

TODO

Currently this library does not support nested partial update.

License

This code is free to use under the terms of the MIT license.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var SkipConditions = []func(reflect.StructField) bool{
	SkipReadOnly,
}

SkipConditions collection of all skip conditions

Updaters collection of all type updaters

Functions

func BoolUpdater

func BoolUpdater(fieldValue reflect.Value, v reflect.Value) bool

BoolUpdater update bool (pointer or value)

func FloatUpdater

func FloatUpdater(fieldValue reflect.Value, v reflect.Value) bool

FloatUpdater update int (any float type Float8, Float16, Float32, Float64 and whether its a pointer or a value)

func IntUpdater

func IntUpdater(fieldValue reflect.Value, v reflect.Value) bool

IntUpdater update int (any int type Int8, Int16, Int32, Int64 and whether its a pointer or a value)

func MapStringInterfaceUpdater

func MapStringInterfaceUpdater(fieldValue reflect.Value, v reflect.Value) bool

MapStringInterfaceUpdater update map[string]interface{}

func NullBoolUpdater

func NullBoolUpdater(fieldValue reflect.Value, v reflect.Value) bool

NullBoolUpdater update null.Bool

func NullFloatUpdater

func NullFloatUpdater(fieldValue reflect.Value, v reflect.Value) bool

NullFloatUpdater update null.Float64

func NullIntUpdater

func NullIntUpdater(fieldValue reflect.Value, v reflect.Value) bool

NullIntUpdater update null.Int

func NullStringUpdater

func NullStringUpdater(fieldValue reflect.Value, v reflect.Value) bool

NullStringUpdater update null.String

func NullTimeUpdater

func NullTimeUpdater(fieldValue reflect.Value, v reflect.Value) bool

NullTimeUpdater update null.Time

func PartialUpdate

func PartialUpdate(dest interface{}, partial map[string]interface{}, tagName string, skipConditions []func(reflect.StructField) bool, updaters []func(reflect.Value, reflect.Value) bool) ([]string, error)

PartialUpdate updates destination object (Must be a pointer to a struct) from a map[string]interface{} where struct tag name is equals to the map key. This function can extended through updaters. A list of function that accepts destination Value and the to be assigned Value and return true if updates is successful Returns list of struct field names that was successfully updated.

func SkipReadOnly

func SkipReadOnly(field reflect.StructField) bool

SkipReadOnly skips all field that has tag readonly

func TimeUpdater

func TimeUpdater(fieldValue reflect.Value, v reflect.Value) bool

TimeUpdater update time (pointer or value)

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL