14

I am currently facing an issue where an external system passes back an 18-digit Id that our platform has previously sent to them. However in the process the capitalization of the Id has been removed - read everything has become uppercase. I would imagine somebody has faced this problem before, but apparantly this is not the case.

So in short; is somebody aware of any existing (apex-) function that converts uppercased 18-digit Ids to a valid Salesforce Id?

Please beware; while a lot of answers exist for converting 15 to 18 digits, which is trivial, this is a different problem.

2
  • I don't think that conversion you are asking is possible. As a solution you can use a Map<String, sObjectt> having String as 18-digit Id in uppercase.
    – o-lexi
    Commented May 24, 2016 at 19:21
  • 6
    You might find your answer here: Creating a link using an 18 character ID
    – Adrian Larson
    Commented May 24, 2016 at 19:22

6 Answers 6

14

I ported Daniel Ballinger's answer here to Apex:

static String CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345';

static List<Boolean> getBitPattern(String c)
{
    Integer index = CHARS.indexOf(c.toUpperCase());
    List<Boolean> result = new List<Boolean>();
    for (Integer bitNumber = 0; bitNumber < 5; bitNumber++)
        result.add((index & (1 << bitNumber)) != 0);
    return result;
}
static String repairCasing(String input)
{
    if (String.isBlank(input) || input.length() != 18) return input;

    List<Boolean> toUpper = new List<Boolean>();
    toUpper.addAll(getBitPattern(String.valueOf(input.substring(15, 16))));
    toUpper.addAll(getBitPattern(String.valueOf(input.substring(16, 17))));
    toUpper.addAll(getBitPattern(String.valueOf(input.substring(17, 18))));

    String output = '';
    for (Integer i = 0; i < 15; i++)
    {
        String c = String.valueOf(input.substring(i, i+1));
        output += toUpper[i] ? c.toUpperCase() : c.toLowerCase();
    }
    output += input.substring(15, 18).toUpperCase();
    return output;
}

I tested it and it worked:

Id value1 = Id.valueOf('00129000007Kbn7AAC');
Id value2 = Id.valueOf('00129000007KBN7AAC');

system.assertEquals(value1, repairCasing(value2));
7
  • can you explain how toUpper[i] ? c.toUpperCase() : c.toLowerCase() works? I'm trying to recreate this in python now and I'm not familiar with that syntax
    – Jwok
    Commented Nov 30, 2018 at 1:16
  • 1
    FYI it's ternary syntax. In Python you'd use c.upper() if toUpper[i] else c.lower().
    – Adrian Larson
    Commented Nov 30, 2018 at 21:34
  • 1
    got it figured out and posted it to this thread. I also created a new post on stack overflow here for a signal boost
    – Jwok
    Commented Dec 1, 2018 at 18:34
  • 2
    There's a typo on line 16 - should be input.substring(15, 16). This won't matter for most Ids (as most start with numbers) but any Ids that start with letters will have all those letters made lowercase.
    – YodaDaCoda
    Commented May 31, 2019 at 1:42
  • 1
    Interesting. It did work before. I'll have to test it out before I update the post content.
    – Adrian Larson
    Commented May 31, 2019 at 1:43
10

Parallel to Adrian's response I also cooked up my own version for reference;

public static Id correctCapitalizedId(String input) {
    String keyString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345', result = '';
    Integer i = 0, s = 15;

    if (input.length() != 18) {
        return null;
    }

    for (String seq : input.split('(?<=\\G.{5})')) {
        Integer chk = keyString.indexOf(input.mid(s++,1));
        i = 0;
        for (String chr : seq.split('')) {
            result += seq.length() != 5  ? '' : (chk & (i == 0 ? ++i : (i*=2))) == 0 ? chr.toLowerCase() : chr;
        }
    }
    return Id.valueOf(result);
}
3
  • 1
    Elegant. Way to take the cleanup a step further!
    – Adrian Larson
    Commented May 24, 2016 at 20:37
  • 2
    Had to edit my code slightly - it was not working correctly in the sense that the last 3 characters were always lowercased - this causes Id.valueOf to render an Id - but subsequently actually using this Id while trying to populate/commit a lookup-field will still render a nasty error....
    – Koen Faro
    Commented May 24, 2016 at 21:46
  • This fails if any part of the 3 character checksum is lowercase. Adding input = input.toUpperCase() at the top of the function fixes the problem. Commented Jun 16, 2023 at 18:44
3

Old thread, but new apex specific solution: As of version 54.0 you can simple use Id.valueOf(str, restoreCasing), see here for more information: https://developer.salesforce.com/docs/atlas.en-us.236.0.apexref.meta/apexref/apex_methods_system_id.htm#apex_System_id_valueOf_2

1

Given that the 18 character form of an ID is designed to be a unique identifier irrespective of the casing, I'm not sure why you would ever need to convert to a 15 digit ID.

Why not stay with the 18 character values? If you did for example need to use them as string map keys just normalise their case by using toLowerCase() on them. They remain valid ID values and can be converted to the type Id using Id.valueOf.

13
  • 2
    Purpose here was actually querying for the record indeed!
    – Koen Faro
    Commented May 24, 2016 at 20:40
  • 1
    Thats what I assumed too @KeithC, until I noticed that my test-case only had uppercase characters to start with and things broke down in production!
    – Koen Faro
    Commented May 24, 2016 at 20:50
  • 1
    @KoenFaro OK I did a bit more reading and I get it now. Correctly cased 18 chars or 15 chars work in queries but if case is lost code needs running to use the extra info in the extra 3 character os the 18 chars to recover the correct casing of the 15 chars. Seems like a method that should be present on the Id class but isn't.
    – Keith C
    Commented May 24, 2016 at 21:01
  • 1
    I would actually expect it to be part of the native Id.valueOf function, rather strange that SF skipped implementing the few lines of code above, sounds like a rush job of adding 18 character Ids - if you open the idea I'll vote!
    – Koen Faro
    Commented May 24, 2016 at 21:09
  • 1
    @KoenFaro If I believed opening developer-oriented ideas made any difference I would...
    – Keith C
    Commented May 24, 2016 at 21:27
1

In case anyone else is using it, I took Adrian Larson's apex code and converted it to Python.

def getBitPatterns(c):
    CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
    index = CHARS.find(c)
    result = []
    for bitNumber in range(0,5):
        result.append((index & (1 << bitNumber)) != 0)
    return result

def repairCasing(inp):
    if len(inp) < 18:
        return 'Error'
    toUpper = []
    toUpper.append(getBitPatterns(inp[15:16]))
    toUpper.append(getBitPatterns(inp[16:17]))
    toUpper.append(getBitPatterns(inp[17:18]))
    toUpper = [item for sublist in toUpper for item in sublist]

    output = ''

    for i in range(0,15):
        c = inp[i:i+1]
        if toUpper[i]:
            output += c.upper()
        else:
            output += c.lower()

    output += inp[15:18].upper()

    return output
2
  • 1
    I think you can get the last 12 lines down to one with a list comprehension if you like. return ''.join([x.upper() if x in toUpper else x.lower() for x in inp[:15]] + inp[15:].upper()
    – David Reed
    Commented Nov 30, 2018 at 19:24
  • I messed it up, of course. in toUpper should just be toUpper[x].
    – David Reed
    Commented Dec 1, 2018 at 20:35
1

If anyone needs to do this with PHP, I ported Adrian's answer. Requires PHP 8.0 or newer:

<?php

declare(strict_types=1);

namespace Shadowhand\Salesforce;

use function assert;
use function strlen;
use function strpos;
use function strtolower;
use function strtoupper;
use function substr;

/**
 * @link https://salesforce.stackexchange.com/questions/123217/converting-uppercased-18-digit-id-to-valid-id
 */
final class SalesforceId
{
    private const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345';

    public static function correct(string $input): string
    {
        assert(assertion: strlen($input) === 18);

        $id = '';

        $capitalize = [
            ...self::shouldCapitalize(letter: substr($input, offset: 15, length: 1)),
            ...self::shouldCapitalize(letter: substr($input, offset: 16, length: 1)),
            ...self::shouldCapitalize(letter: substr($input, offset: 17, length: 1)),
        ];

        for ($i = 0; $i < 15; $i++) {
            $letter = substr($input, offset: $i, length: 1);

            $id .= $capitalize[$i] ? strtoupper($letter) : strtolower($letter);
        }

        $id .= strtoupper(substr($input, offset: 15, length: 3));

        return $id;
    }

    private static function shouldCapitalize(string $letter): array
    {
        $index = strpos(haystack: self::CHARS, needle: strtoupper($letter));
        $result = [];

        for ($bit = 0; $bit < 5; $bit++) {
            $result[] = ($index & (1 << $bit)) !== 0;
        }

        return $result;
    }

    private function __construct()
    {
    }
}

And here is the PHPUnit test:

<?php

declare(strict_types=1);

namespace Shadowhand\Salesforce;

use PHPUnit\Framework\TestCase;

use function strtolower;
use function strtoupper;

class SalesforceIdTest extends TestCase
{
    public function testShouldCorrectCapitalization(): void
    {
        $id = '00129000007Kbn7AAC';

        self::assertSame(expected: $id, actual: SalesforceId::correct(strtoupper($id)));
        self::assertSame(expected: $id, actual: SalesforceId::correct(strtolower($id)));
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .