16

I have

Schema.SObjectField field = MyObject__c.myField__c;
Schema.SObjectType objectType = getTypeOf(field);

how would getTypeOfField() need to look like for this to be true?

System.assertEquals(MyObject__c.SObjectType, objectType);
6
11

I may be missing the boat here but why not a method based on the following?

// Build a map of hashcodes for each fieldDescribe taken from Schema Global Describe
Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
Map<Integer,Schema.SObjectType> fldHashCodeToSObjectTypeMap = new map<Integer,Schema.SObjectType>();

for (String sobjname: gd.keySet()) {
    for (Schema.SObjectField sobjField : gd.get(sObjName).getDescribe().fields.getMap().values())
        fldHashCodeToSObjectTypeMap.put(((Object) sObjField).hashCode(),gd.get(sobjName));

}

// Then compare your sobjectField in hand by hashing it and then looking up into the map
Schema.SObjectField field0 = Account.Industry; 
Integer field0Hash = ((Object) field0).hashCode();
Schema.SObjectField field1 = Opportunity.CloseDate; 
Integer field1Hash = ((Object) field1).hashCode();

System.debug(LoggingLevel.INFO,'field0 belongs to sobject:' + fldHashCodeToSObjectTypeMap.get(field0Hash)); 
System.debug(LoggingLevel.INFO,'field1 belongs to sobject:' + fldHashCodeToSObjectTypeMap.get(field1Hash));

Debug output yields:

17:58:06.113 (1113461278)|USER_DEBUG|[16]|INFO|field0 belongs to sobject:Account
17:58:06.113 (1113547114)|USER_DEBUG|[17]|INFO|field1 belongs to sobject:Opportunity

Your Util.getType(someSobjectField) method would simply do

fldHashCodeToSObjectTypeMap.get((Object) someSobjectField).hashCode());

on some static variable instantiation of fldHashCodeToSObjectTypeMap

5
  • hashCode func is an interesting angle - looks good! – Matt and Neil Jan 14 '16 at 2:24
  • @bigassforce : this is what happens when one reads too many SFSE answers from smarter folks than me; odd ideas show up in my brain – cropredy Jan 14 '16 at 2:28
  • I think I don't really like this because what you do is not asking a encapsulated system for an answer bu store all possible answers in a map before and than just retrieve them. – Robert Sösemann Jan 15 '16 at 15:34
  • oh well. At least your 50 pt incentive inspired me to learn something new. – cropredy Jan 15 '16 at 16:02
  • 1
    Sure @crop1645 thanks for sharing your idea. It's better than leaving it unsolved. But actually I expected some Salesforce internals to tell me there is an undocumented method getSObjectType() on SObjectField ;-) – Robert Sösemann Jan 15 '16 at 18:58
10
+50

While I really hope there is an easier way to achieve this, the Tooling API might be one candidate.

You can interrogate the Salesforce Tooling API with an Apex library or just use the code below by itself:

  1. First specify your field name:

    //choose your field name (without suffix!)
    String fieldName = yourField.getDescribe().getName().substringBefore('__c');
    System.debug(fieldName);
    

    'Status'

  2. Then obtain the Custom Object Id (eg 01I...) or Table Enum (eg Account) which it lives on:

    String query = 'SELECT TableEnumOrId From CustomField WHERE DeveloperName = \'' + fieldName + '\'';
    String endpoint = Url.getSalesforceBaseUrl().toExternalForm();
    endpoint += '/services/data/v29.0/tooling/query/?q=';
    endpoint += EncodingUtil.urlEncode(query, 'UTF-8');
    
    HttpRequest request = new HttpRequest();
    request.setMethod('GET');
    request.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
    request.setEndpoint(endpoint);
    
    HttpResponse response = new Http().send(request);
    String tableEnumOrId = response.getBody().substringAfter('"TableEnumOrId":"').substringBefore('"');
    
    System.debug(tableEnumOrId);
    

    '01Ib0000000CM8TEAW'

  3. Finally, you can pull the API / Developer Name off the Custom Object definition itself:

    query = 'SELECT DeveloperName From CustomObject Where Id = \'' + tableEnumOrId + '\'';
    endpoint = Url.getSalesforceBaseUrl().toExternalForm();
    endpoint += '/services/data/v29.0/tooling/query/?q=';
    endpoint += EncodingUtil.urlEncode(query, 'UTF-8');
    
    request = new HttpRequest();
    request.setMethod('GET');
    request.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
    request.setEndpoint(endpoint);
    
    response = new Http().send(request);
    System.debug(response.getBody());
    String customObjectName = response.getBody().substringAfter('"FullName":"').substringBefore('"');
    
    System.debug(customObjectName);
    

    'Schematic'

  4. Use Type.forName to get at your desired SObject, SObjectType, or DescribeSObjectResult:

    Type reflector = Type.forName('Invoice__c');
    SObject obj = (SObject)reflector.newInstance();
    SObjectType objectType = obj.getSObjectType();
    //now you can do objectType.getDescribe etc...
    

There are a number of caveats which would need serious 'hardening':

  • you may need to truncate or append the __c as necessary in the Tooling API queries
  • in managed context you will need to play with the namespace prefix.
  • gonna be tricky if you have multiple fields with the same name!
2
  • 4
    Just wondering how this would work if i had the same field name on multiple custom objects? e.g. Total on object A and object B? – Andrew Fawcett Apr 11 '14 at 15:00
  • Wouldn't work, thanks for spotting! Need to query TableEnumOrId from CustomObject beforehand @AndrewFawcett – Matt and Neil Oct 17 '17 at 12:42
2

These are just two additional hacks to get the information until SF finally provides a propper getter. The first is faster, the second is solid.

Faster Approach:

SF obviously stores the information on the field internally and use it for the Error message:

System.SObjectException: MyNamespace__MyCustomObject__c.Name does not belong to SObject type Account

I decided to simply use this information. I know it's a little fragile since we never know what they will change internally, but since we don't have any stable, lean alternative and I don't want to do API callouts for simple information like this, I decided to go this way.

private static final Map<String, SObjectType> SOBJECT_TYPES = Schema.getGlobalDescribe();
private final SObjectField field;

public SObjectType sObjectType() {
    try {
        new Account().put(field, null);
        return Account.SObjectType;
    }
    catch (SObjectException e) {
        return SOBJECT_TYPES.get(e.getMessage().substringBefore('.'));
    }
}

It works for custom, standard, and namespaced types. I also did some performance testing and can say, that it takes about twice as long as getting the lookups parent type as we all do it sometimes something similar to this: field.getDescribe().getReferenceTo()[0].

Solid Approach:

Another approach is a roundtrip through the parent type. There we know all the child relationships, so what we need to do, is find the child relationship, that belongs to this field. And this child relationship now also knows the child's Type.

public SObjectType sObjectTypeStable() {
    for(ChildRelationship relationship : parentType().getDescribe().getChildRelationships()) {
        if(relationship.getField() == field) {
            return relationship.getChildSObject();
        }
    }
    return null;
}

Compared to the try/catch approach it multiplies the execution time by a factor of 10 or more (probably varies by the ChildReference's index), so I do not recommend it!

GIST:

This gist contains the example class. For sure, it could be performance-optimized, but in real-world scenarios, I would not benefit from any additional caching so I skipped it.

This is how its used:

SObjectField field = Contact.AccountId;
SObjectType type = new FieldDescribe(field).sObjectType();
SObjectType stable = new FieldDescribe(field).sObjectTypeStable();
SObjectType parent = new FieldDescribe(field).parentType();
List<SObjectType> parents = new FieldDescribe(field).parentTypes();
4
  • Exceptions are super slow in Apex. Is your "Faster Approach" really that? – Adrian Larson Jul 1 at 14:35
  • Sorry, I meant one of my two approaches is 10 times faster than the other (solid) approach. Sorry, if this was not clear through the description. But compared to most of the others I would say it is simpler and faster, doesn't need API callouts, doesn't need huge metadata caches or computation, and is easy to read and understand. So yes, Exceptions are slow, but there will be a tradeoff as long as SF does not provide the feature and in my subjective understanding, this is the best so far. – Basti Jul 1 at 16:21
  • Nothing to apologize for, it was an honest question. – Adrian Larson Jul 1 at 17:13
  • Alright, Yes, I did a bunch of tests, executing the two approaches 10.000 times in a loop and compared the results with the 'straightforward' parent type retrieval. The fastest was the parent type with about 500ms, then the Exception with about 1000ms, and then the ChildRelationships with about 10.000ms for 10k executions. – Basti Jul 1 at 17:27
1

I know this is a little old, but I was searching how to solve this problem and I couldn't find anything useful. This is how I solved it:

Type myObjectType = MyObject__c.myField__c.class;
String myObjectName = myObjectType.toString(); // You can use getName() as well

That code will give you the Type of MyObject__c class. Then you can use all the methods for Type class in Apex like toString() or getName() to get the String name of MyObject__c.

Please check the Salesforce Type Class for more info.

1
  • 2
    This will not work when just have an instance of Schema.SObjectField instead of MyObject__c.myField__c. Remember my initial question is how to get the Schema.SObjectType for a given Schema.SObjectField. – Robert Sösemann Jun 23 '20 at 13:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.