Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fauna-go should ignore zero values when serializing/saving #123

Closed
mscno opened this issue Jan 5, 2021 · 4 comments · Fixed by #134
Closed

Fauna-go should ignore zero values when serializing/saving #123

mscno opened this issue Jan 5, 2021 · 4 comments · Fixed by #134

Comments

@mscno
Copy link

mscno commented Jan 5, 2021

Issue

Currently fauna-go sets values in the document even though they have a zero value.

Example:

running the following query will result in the following document:

type MyObject struct {
	MyString string
	MyInt int
}

myObj := MyObject{
	MyString: "",
	MyInt:    0,
}

res, err := s.fdb.Query(f.Create(f.Collection("my_collection"), f.Obj{"data": myObj}))
if err != nil {
	return nil, err
}

document:

{
  "ref": Ref(Collection("my_collection"), "286888125244899841"),
  "ts": 1609856686740000,
  "data": {
    "MyString": "",
    "MyInt": 0
  }
}

Suggested Solution

Fauna-db should implement functionality to discard zero valued fields. I.e. string values of "", int values of 0, float values of 0 should be discared.

I think this should be implemented via field struct tags, either via a opt-in to use serialize the zero values, or via a opt out. This is how serialization/deserialization of zero values in handling in other db drivers and in JSON.

For example the JSON field tags have the option to specify omitempty, which discards zero valued fields from serialization.

type MyObject struct {
	MyString string `json:",omitempty"`
	MyInt int `json:",omitempty"`
}

Conversely pg-go defaults to discarded zero values, but allows you to specify use_zero

type MyObject struct {
	MyString string `pg:",use_zero"`
	MyInt int `pg:",use_zero"`
}

I would propose that this is implemented using the omitempty option in the struct field tags. E.g:

type MyObject struct {
	MyString string `fauna:",omitempty"`
	MyInt int `fauna:",omitempty"`
}
@mscno mscno changed the title Fauna ignore zero values Fauna-go should ignore zero values when serializing/saving Jan 5, 2021
@trevorsibanda
Copy link

Thank you for opening this @mscno

I admit it would be simpler to have an omitempty option, in the meantime, similar to pg-go you can model nullable fields using pointers. See https://github.com/fauna/faunadb-go/blob/master/faunadb/serialization_test.go#L188

@mscno
Copy link
Author

mscno commented Jan 5, 2021

I think requiring null values to be modeled using pointers makes it a little bit more chaotic.

Let me try to crack up a working solution to parse omitempty from the field tags.

Will submit PR later today.

@mscno
Copy link
Author

mscno commented Jan 14, 2021

Another update on this issue, with details on a use case where pointers are not preferable is when updating existing structs:

Say I have a struct as follows

type Person struct {
	Email string `fauna:"email"`
	Name int `fauna:"name"`
}

Lets say I have strictly ensured that these columns are never null, and therefore I prefer to treat them as strings rather than pointers

Now I want to update a struct and change the email of a person. For example because I received a put request from the user. If this put object contained only the email, I would do the following:

If I pass in the customer struct like so:

myPerson := Person{Email: newEmail}

res, err := faunaClient.Query(f.Update(ref,&myPerson))

this would cause the Name field in the database to be overwritten with "", which is not I what I want.

Now am stuck with two options:

  1. model the fields as nullable, even though they are not nullable and never have zero values. Which causes we to introduce unnecessary nil checks in the rest of the code.
  2. manually create the FQL query for only updated a subset of the fields. This might be easy for the above struct, but imagine a large struct with hundreds of fields and nested objects where I only want to up date a single field, then it would be a tedious proces..

Implementing omitempty option elegantly solves this by never serializing and transmitting values that have zero values.

@mscno
Copy link
Author

mscno commented Jan 14, 2021

Update: Option 1 above doesnt work either, as a pointer string field with nil value, will override and delete any existing data in the field, possibly causing unintended dataloss:

Example:

//Struct
	person := &Person{
		Name:  &name,
		Email: &email,
	}

	personObj := f.Obj{"data":&person}a
	fmt.Println(personObj.String())
  // prints: 'Obj{"data": Obj{"Name": "John Doe", "Email": "somemail@mail.com"}}'


	newEmail := "newemail@mail.com"
	personEmailOnly := &Person{
		Email:  &newEmail,
	}
	personEmailOnlyObj := f.Obj{"data":&personEmailOnly}

	fmt.Println(personEmailOnlyObj.String())
  // prints: 'Obj{"data": Obj{"Name": nil, "Email": "newemail@mail.com"}}'


	res, err := s.fdb.Query(f.Create(f.Collection("persons"),personObj))
	if err != nil {
		return err
	}
	var personRef f.RefV
	_ = res.At(ref).Get(&personRef)

	res, err = s.fdb.Query(f.Update(personRef,personEmailOnlyObj))
	if err != nil {
		return err
	}
	newPerson := Person{}
	res.At(data).Get(&newPerson)

	fmt.Println("name:",newPerson.Name," | email: ",newPerson.Email)
  // prints: 'name: <nil>  | email:  0xc013cf2480
'

In the above case, we create a person with both email and name. Then afterwards we update the persons email, having a struct that has "nil" at the Name field. This causes as the resulting value in the name field in the database to be deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants