4
votes

We store the user's contactId in our database and use it to fetch/edit/delete contacts when the user requests from our app. Now that Google Contacts API is to be sunset, we need to call Google People API for these actions using resource id.

Is there any way to get Google People API resource ID from Google Contacts contact Id?

I found the following answer in Stackoverflow:

The Contacts API and People API are separate API's not meant to interop with each other.

That said, the reverse engineered way to do this is to take the Contacts API contact ID, parse that hexadecimal value, convert it to decimal, and add a 'c' prefix to it, and that becomes the People API person resource ID.

e.g. if the Contacts API contact ID was 100, then the People API person ID would be c256. When you fetch contacts from the People API in this fashion, it'll have the joined profile information, if present.

I tried the solution and it is working, but need to know whether we can use this conversion error free (i.e without any exceptions) to populate the respective resource id to the contact id.

2

2 Answers

1
votes

Answer:

While the workaround provided in this answer seems to work, I think there's a more documented way to find the People API's resourceName based on the contactId.

This contactId is part of the Person resource, as long as the connection is coming from CONTACTS.

More specifically, this information can be found as part of the resource at:

{
  "resourceName": "people/c...",
  "metadata": {
    "sources": [
      {
        "type": "CONTACT",
        "id": "CONTACT_ID" // Converting to decimal and adding "people/c" results in resource name
      }
    ]
  }
}

Therefore, if you have an array of valid contactId, you could find the corresponding resourceNames by listing your connections (see people.connections.list) and checking whether the contactId matches the id in the nested field and retrieving the resourceName for that connection if that's the case.

Code sample:

For example, using Apps Script, you could retrieve a mapping of contactId and resourceName, like this:

function getResourceNamesFromContactIds(contactIds) { // contactIds: array with valid contactIds
  const resourceName = "people/me";
  const optionalArgs = {
    personFields: "metadata",
    sources: "READ_SOURCE_TYPE_CONTACT"
  }
  const connections = People.People.Connections.list(resourceName, optionalArgs)["connections"];
  const mapping = contactIds.map(contactId => {
    const foundConnection = connections.find(connection => {
      return contactId == connection["metadata"]["sources"][0]["id"];
    });
    if (foundConnection) {
      return {
        contactId: contactId,
        resourceName: foundConnection["resourceName"]
      }
    }
  }).filter(el => el);
  return mapping;  
}
0
votes

The accepted answer doesn't work for contacts that don't have email addresses.

There's no need to get the metadata object from person.emailAddresses, we can get it directly from the person object which doesn't require any other field to be present in the person's info.

Also, the accepted answer assumes the first source is always of type "CONTACT" which may not be the case, in which case you might get the wrong contact-id

Here's the improved version (in Java):

List<Person> people = getPeople();// fetch all pages of the user contacts, with "personFields" set to "metadata" only.

Map<String, String> mapping = new HashMap<>();

people.forEach(person -> {
    PersonMetadata metadata = person.getMetadata();
    if (metadata != null) {
        List<Source> sources = metadata.getSources();
        if (sources != null) {
            sources.stream()
                .filter(srcObj -> "CONTACT".equalsIgnoreCase(srcObj.getType()))
                .findFirst()
                .ifPresent(source ->
                    mapping.put(source.getId(), person.getResourceName()));
        }
    }
});