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

Convert DirectoryObjects to User objects? #79

Closed
hanix opened this issue Feb 15, 2022 · 11 comments
Closed

Convert DirectoryObjects to User objects? #79

hanix opened this issue Feb 15, 2022 · 11 comments
Assignees
Labels
duplicate This issue or pull request already exists enhancement New feature or request

Comments

@hanix
Copy link

hanix commented Feb 15, 2022

Hi, I am using the GO SDK to fetch users and group information from AD. Because I have a lot of users, I am fetching groups first and then foreach group I get it's members.

func fetchGroupMembers(*groups graph.Group) {
	var dirObjList []graph.DirectoryObject = make([]graph.DirectoryObject, 0)
	
	for _, group := range groups {
		page1, err := client.GroupsById(*group.GetId()).Members().Get(nil)
		if err != nil {
			return err
		}
		dirObjList = append(dirObjList, page1.GetValue()...)

		//Iterator to iterate over remaining pages and fetch users
		iterator, err := msgcore.NewPageIterator(page1, *adapter, func() serialization.Parsable {
			return directoryobjects.NewDirectoryObjectsResponse()
		})
		if err != nil {
			return err
		}
		callback := func(item interface{}) bool {
			dirObjList = append(dirObjList, item.(graph.DirectoryObject))
			return true
		}
		iterator.Iterate(callback)
		fmt.Println("#Users: ", len(dirObjList))

		//Here, I am expecting users object but I get directory objects
		for _, u := range dirObjList {

			//I need to access user object attributes like displayName and so on ...
		}

		match = false
	}
}

Is there a way to convert graph.DirectoryObjects to graph.User objects? Or how should I go about this? Thanks.
PS: This code is representational only.

@ghost ghost added the Needs Triage 🔍 label Feb 15, 2022
@baywet
Copy link
Member

baywet commented Feb 15, 2022

Hi @hanix,
Thanks for trying the SDK and for reaching out.
This is listed as a known limitation and needs to be implemented in the generator first.
The good news is that I started the work in the generator, but we're probably still as least one week out before this shows up in the SDK.

When this is complete, the callback will provide you the final object, up-casted as directoryObject. You'll need to downcast to a User object and you'll see the user-specific properties with values.

In the meantime, you can access the properties you need from user through additional data (user-> directoryObject-> entity)

func (m *Entity) GetAdditionalData()(map[string]interface{}) {

@baywet baywet self-assigned this Feb 15, 2022
@baywet
Copy link
Member

baywet commented Feb 15, 2022

if that can help for future reference once the feature is implemented, you can "downcast" like this

// response is a directory object response like for client.GroupsById("id").Members.Get(nil), where the value array is an array of directory objects
// m is the import for the models package
// absser is the import for github.com/microsoft/kiota/abstractions/go/serialization
for _, do := range response.GetValue() {
		pdo := absser.Parsable(&do)
		user := pdo.(*m.User)
		upn := user.GetUserPrincipalName()
		fmt.Printf("UPN: %v\n", *upn)
	}

@hanix
Copy link
Author

hanix commented Feb 16, 2022

Thank you @baywet for replying, I will be waiting for the enhancement to be released. Could you provide an example of how I could use the user->directoryObject->entity to access the user properties? I I played with it a little bit I wasn't able to produce anything ?

@hanix
Copy link
Author

hanix commented Feb 16, 2022

One more thing, I noticed that user object (and possibly) others don't support json.marshaling. The attributes are not Exported nor do they have the json tag.

Should I open a new issue for this as well ?
Thanks

@baywet
Copy link
Member

baywet commented Feb 16, 2022

Update: the casting will look something like that instead after some more research on my end. This is because of the way the type system works in go (composition over inheritance for structs) and the fact that we need to cast to something to assign the properties, up-casting to the "parent type" creates panics during deserialization. Asserting to an interface type instead works. So we'll need to introduce interfaces for the models

// response is a directory object response like for client.GroupsById("id").Members.Get(nil), where the value array is an array of directory objects
// m is the import for the models package
for _, do := range response.GetValue() {
		user := do.(m.Userable)
		upn := user.GetUserPrincipalName()
		fmt.Printf("UPN: %v\n", *upn)
	}

For your question about accessing the properties currently, here is a similar example

// response is a directory object response like for client.GroupsById("id").Members.Get(nil), where the value array is an array of directory objects
for _, do := range response.GetValue() {
		upn := do.GetAdditionalData()["userPrincipalName"] // upn will be nil if not a user, might want to nil check before dereferencing
		fmt.Printf("UPN: %v\n", *upn)
	}

As per the unmarshalling aspect. It's not a bug, it's a feature. With this new generation of SDKs we don't want to be tied to any given serialization format and/or library/implementation. This is one of the reasons why we have a high separation of concerns. Additionally, most JSON libs out there (or unmarshall for Go) don't support advanced aspects like deserialization to a different type depending on a property value (what we're talking about here), or if they do it's either complex or relies on slow reflection.
more information on our design choices

@hanix
Copy link
Author

hanix commented Feb 16, 2022

Thank you.
I will keep up with the progress of the enhancements : )

@baywet
Copy link
Member

baywet commented Feb 16, 2022

you're welcome!
I'll go ahead and close this issue since we're already tracking it at other places. I suggest you subscribe to this issue to track progress.

@baywet baywet closed this as completed Feb 16, 2022
@baywet baywet added duplicate This issue or pull request already exists and removed Needs Attention 👋 labels Feb 16, 2022
@ronsun8
Copy link

ronsun8 commented Feb 22, 2022

I'm having the same problem as OP and waiting for the iterator enhancement.

In the meantime, I tried your example but I'm unable to dereference upn from your example. It says gives me invalid indirect of upn type 'interface{}'

I found a method to get the value using reflect below:

additionalData := dirObj.GetAdditionalData()
i := additionalData["displayName"]
if i != nil {
  test := reflect.ValueOf(i).Elem()
  fmt.Printf("%v", test)
}

Is this the recommended way to do it or am I missing something?

@baywet
Copy link
Member

baywet commented Feb 22, 2022

You might have to add a type assertion to the sample I've provided. So

// response is a directory object response like for client.GroupsById("id").Members.Get(nil), where the value array is an array of directory objects
for _, do := range response.GetValue() {
upn := do.GetAdditionalData()["userPrincipalName"].(*string) // upn will be nil if not a user, might want to nil check before dereferencing
fmt.Printf("UPN: %v\n", *upn)
}

@ronsun8
Copy link

ronsun8 commented Feb 23, 2022

it worked great, thanks for the quick reply!

@baywet
Copy link
Member

baywet commented Feb 23, 2022

You're welcome!
If you push this code to production there are a bunch of nil checks missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate This issue or pull request already exists enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants