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

Cannot get nil value from BackingStore with delta query when a property value was changed to nil (empty) #725

Open
t2y opened this issue Jun 6, 2024 · 7 comments
Assignees

Comments

@t2y
Copy link

t2y commented Jun 6, 2024

I am trying to implement the delta query to track user changes.

For example, I changed First name and Last name to empty on https://entra.microsoft.com/ .

スクリーンショット 2024-06-06 14 21 58

I got the below response. You can see givenName and surname are detected as null values.

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
  "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta()?$deltatoken=dqOf4D0FeifmTE8LZ08sKG_0pMPP7gZ...",
  "value": [
    {
      "givenName": null,
      "surname": null,
      "id": "a8d67d70-f83b-453d-9666-1f280a609bf6"
    }
  ]
}

But, I cannot know whether the givenName and surname were changed to null since the Enumerate() or EnumerateKeysForValuesChangedToNil() methods don't return the values (nil).

var user models.Userable
...
b := user.GetBackingStore()
fmt.Println(b.GetInitializationCompleted())
fmt.Println(b.GetReturnOnlyChangedValues())
for i, v := range b.EnumerateKeysForValuesChangedToNil() {
	fmt.Printf(" - %v, %+v\n", i, v)
}
for k, v := range b.Enumerate() {
	fmt.Printf(" - %s, %+v\n", k, v)
}
- additionalData, map[]
- odataType, 0x140005803b0
- id, 0x14000580320

Another sample code to confirm the user data.

json, _ := serialization.SerializeToJson(user)
fmt.Println(string(json))

Also, the user data doesn't have both givenName and surname properties.

{"id":"a8d67d70-f83b-453d-9666-1f280a609bf6","@odata.type":"#microsoft.graph.user"}

In my debugging, InMemoryBackingStore doesn't handle a nil value in User.GetFieldDeserializers() method.

res["surname"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error {
	val, err := n.GetStringValue()
	if err != nil {
		return err
	}
	if val != nil {
		m.SetSurname(val)
	}
	return nil
}
func (m *User) SetSurname(value *string) {
	err := m.GetBackingStore().Set("surname", value)
	if err != nil {
		panic(err)
	}
}

Could you tell me how do I check both givenName and surname properties were changed to nil via msgraph-sdk-go API?

Reference

@rkodev rkodev assigned rkodev and unassigned rkodev Jul 10, 2024
@rkodev
Copy link
Contributor

rkodev commented Jul 15, 2024

Hi @baywet @andrueastman This is a case where the json payload has explicitly defined a field as null, and the intention is to differentiate fields that were not returned but are with a nil value, vs fields that were explicitly set as nil because the value is null.

@baywet
Copy link
Member

baywet commented Jul 15, 2024

Thanks for the call out.
This is a design limitation. The enumeration function was designed with serialization and not deserialization in mind.
I guess we could mimic that design for deserialization with an additional enumerate read null keys method or similar.
This would be a breaking change due to the addition of a method in an interface.

Andrew, Ronald, don't hesitate to share suggestions if you have other ideas here.

@andrueastman
Copy link
Member

One thing we could try is play around with the serialization hooks and provide and possibly provide an alternative serialization factory implementation (this would require some investigation and thinking through).

https://github.com/microsoft/kiota-abstractions-go/blob/7677e0c3ce5056593c3c3ea6f3c7411e3e1aa42c/store/backing_store_parse_node_factory.go#L14

By having the alternative implementation have the actions to initialize the store before the json payload is deserialized(rather than after) we could have the store initialized with the values from the API in such a scenario to include nulls and other values....

@rkodev
Copy link
Contributor

rkodev commented Jul 15, 2024

A pointer to an uninitialized value and a nil would evaluate to the same value but different types

type Null interface{}

func checkType(v interface{}) {
	switch v := v.(type) {
	case nil:
		fmt.Println("nil value")
	case *Null:
		fmt.Println("Pointer to Null")
	default:
		fmt.Printf("Unknown type: %T\n", v)
	}
}

func main() {
	var a *Null
	var b *int

	checkType(nil) // nil value
	checkType(a)   // Pointer to Null
	checkType(b)   // Unknown type: *int

	if a == nil {
		fmt.Println("a is nil")
	}
	if a == (*Null)(nil) {
		fmt.Println("a is null")
	}
}

a possible solution is to define a Null type and have explicit references to to null from serialization set to its reference. This will require modifying kiota, abstractions and json_serialization, as null value serialization should give the same result when compared to nil, but will allow handling of values explicitly set to null

@baywet
Copy link
Member

baywet commented Jul 15, 2024

Wouldn't any object value (non-nil) satisfy this null interface since it's empty?

@rkodev
Copy link
Contributor

rkodev commented Jul 17, 2024

Yes it will, but it will give the ability to differentiate is a serialized value was null as opposed to if a serialized value was not present at all. As Is the problem we have is that there is no difference between values that were not returned by the server as opposed to values that were returned with an explicitly null value

@baywet
Copy link
Member

baywet commented Jul 17, 2024

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

No branches or pull requests

4 participants