最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How would one convert a UUID type to ULID type? - Stack Overflow

programmeradmin1浏览0评论

Theres a bit documentation out there how to convert ULID to UUID but not so much when you need to convert UUID to ULID.

I'm looking at this UUID / ULID generator / converter /

but I'm not quite sure how I would replicate the UUID to ULID conversion, the source is too obfuscated for me to understand.

I'm not sure where to even begin, is this conversion even safe ? will it guarantee a unique conversion ?

Theres a bit documentation out there how to convert ULID to UUID but not so much when you need to convert UUID to ULID.

I'm looking at this UUID / ULID generator / converter https://www.ulidtools./

but I'm not quite sure how I would replicate the UUID to ULID conversion, the source is too obfuscated for me to understand.

I'm not sure where to even begin, is this conversion even safe ? will it guarantee a unique conversion ?

Share Improve this question asked Dec 13, 2022 at 17:24 user3621898user3621898 6197 silver badges25 bronze badges 2
  • It's just two representations of the same bit pattern. Each one stores 128 bits of information. There's no "safety" question. Why do you need a ULID? – Tim Roberts Commented Dec 13, 2022 at 17:28
  • An external api I'm using only accepts ULIDs for deduplication of events – user3621898 Commented Dec 13, 2022 at 18:02
Add a ment  | 

4 Answers 4

Reset to default 6

I've had the same issue and I took a look at ulidtools. then after digging into its source code for a while I found that it uses this package behind the seen.

import pkg from "id128";
const { Ulid, Uuid4 } = pkg;

const ulid = Ulid.generate();
const ulidToUuid = Uuid4.fromRaw(ulid.toRaw());
const uuidToUlid = Ulid.fromRaw(ulidToUuid.toRaw());

console.table([
  {
    generated_ulid: ulid.toCanonical(),
    converted_to_uuid: ulidToUuid.toCanonical(),
    converted_back_to_ulid: uuidToUlid.toCanonical(),
  },
]);

Based on @ImanHosseiniPour answer I came up with the following:

TIMESTAMP + UUID = ULID

import { Ulid, Uuid4 } from "id128";
import { factory, decodeTime } from 'ulid'

const genUlid = factory();

function convertUuidToUlid(
  timestamp: Date, 
  canonicalUuid: string,
): Ulid {
  const uuid = Uuid4.fromCanonical(canonicalUuid);
  const convertedUlid = Ulid.fromRaw(uuid.toRaw())
  const ulidTimestamp = genUlid(timestamp.valueOf()).slice(0, 10)
  const ulidRandom = convertedUlid.toCanonical().slice(10);
  
  return Ulid.fromCanonical(ulidTimestamp + ulidRandom)
}

const timestamp = new Date()
const uuid = 'f0df59ea-bfe2-43a8-98d4-8213348daeb6'
const ulid = convertUuidToUlid(timestamp, uuid)
const originalUuid = Uuid4.fromRaw(ulid.toRaw());

console.table({
  timestamp: timestamp.valueOf(),
  uuid,
  ulid: ulid.toCanonical(),
  decodedTime: decodeTime(ulid.toCanonical()),
  originalUuid: originalUuid.toCanonical(),
});

Keep in mind that reverse engineering the ULID into a UUID will make the two first chunks different from the original since we incorporated the timestamp

The other answers to this question didn't satisfy my needs (no external dependencies). Here is a version which works with vanilla ECMAScript2018 JS:

/**
 * Decodes a hexadecimal string (case-insensitive) into an equivalent Uint8Array.
 * 
 * @param {string} hexString The string to decode
 * @returns {Uint8Array} The string decoded into binary
 */
function decodeHex(hexString) {
    if (typeof hexString !== 'string' || hexString.length % 2 !== 0) {
        throw new Error('Invalid hex string');
    }

    const decoded = new Uint8Array(hexString.length / 2);

    for (let i = 0; i < hexString.length; i += 2) {
        const byte = parseInt(hexString.substring(i, i + 2), 16);
        decoded[i / 2] = byte;
    }

    return decoded;
}


/**
 * The ULID encoding lookup. Notably excludes I, L, U, and O.
 */
const ULID_ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';

/**
 * Converts a UUID to an equivalent ULID.
 * 
 * @param {string} uuid The UUID, encoded as a 36-character hex-with-dashes string.
 * @returns {string} The equivalent ULID, encoded as a 26-character base32 string.
 */
function uuidToUlid(uuid) {
    if (!/^[0-9A-Fa-f]{8}(-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/i.test(uuid)) {
        throw new Error('Invalid UUID.');
    }

    // Break into sections, excluding dashes.
    // Using 0179e73f-ff38-a5e0-e633-48fae1c0bd35 as example...

    const section1 = uuid.substring(0, 8);      // is 0179e73f
    const section2 = uuid.substring(9, 13);     // is ff38
    const section3 = uuid.substring(14, 18);    // is a5e0
    const section4 = uuid.substring(19, 23);    // is e633
    const section5 = uuid.substring(24);        // is 48fae1c0bd35

    // concatenate the parts, decoded into Uint8Array types. This will have length 16.
    const decodedArray = [
        ...decodeHex(section1),
        ...decodeHex(section2),
        ...decodeHex(section3),
        ...decodeHex(section4),
        ...decodeHex(section5),
    ];

    // optimized unrolled loop for converting 16 bytes into 26 characters, using 
    // the ULID lookup to translate from integers [0-25] to valid ULID characters.
    // ref. https://github./RobThree/NUlid
    const ulid = [
        ULID_ENCODING[(decodedArray[0] & 224) >> 5],
        ULID_ENCODING[decodedArray[0] & 31],
        ULID_ENCODING[(decodedArray[1] & 248) >> 3],
        ULID_ENCODING[((decodedArray[1] & 7) << 2) | ((decodedArray[2] & 192) >> 6)],
        ULID_ENCODING[(decodedArray[2] & 62) >> 1],
        ULID_ENCODING[((decodedArray[2] & 1) << 4) | ((decodedArray[3] & 240) >> 4)],
        ULID_ENCODING[((decodedArray[3] & 15) << 1) | ((decodedArray[4] & 128) >> 7)],
        ULID_ENCODING[(decodedArray[4] & 124) >> 2],
        ULID_ENCODING[((decodedArray[4] & 3) << 3) | ((decodedArray[5] & 224) >> 5)],
        ULID_ENCODING[decodedArray[5] & 31],
        ULID_ENCODING[(decodedArray[6] & 248) >> 3],
        ULID_ENCODING[((decodedArray[6] & 7) << 2) | ((decodedArray[7] & 192) >> 6)],
        ULID_ENCODING[(decodedArray[7] & 62) >> 1],
        ULID_ENCODING[((decodedArray[7] & 1) << 4) | ((decodedArray[8] & 240) >> 4)],
        ULID_ENCODING[((decodedArray[8] & 15) << 1) | ((decodedArray[9] & 128) >> 7)],
        ULID_ENCODING[(decodedArray[9] & 124) >> 2],
        ULID_ENCODING[((decodedArray[9] & 3) << 3) | ((decodedArray[10] & 224) >> 5)],
        ULID_ENCODING[decodedArray[10] & 31],
        ULID_ENCODING[(decodedArray[11] & 248) >> 3],
        ULID_ENCODING[((decodedArray[11] & 7) << 2) | ((decodedArray[12] & 192) >> 6)],
        ULID_ENCODING[(decodedArray[12] & 62) >> 1],
        ULID_ENCODING[((decodedArray[12] & 1) << 4) | ((decodedArray[13] & 240) >> 4)],
        ULID_ENCODING[((decodedArray[13] & 15) << 1) | ((decodedArray[14] & 128) >> 7)],
        ULID_ENCODING[(decodedArray[14] & 124) >> 2],
        ULID_ENCODING[((decodedArray[14] & 3) << 3) | ((decodedArray[15] & 224) >> 5)],
        ULID_ENCODING[decodedArray[15] & 31]
    ].join('');

    return ulid;
}

const uuid = '0179e73f-ff38-a5e0-e633-48fae1c0bd35';
const ulid = uuidToUlid(uuid);
console.log(ulid); // 01F7KKZZSRMQGECCT8ZBGW1F9N

Here is a JSFiddle if you want to verify functionality.

Pure Javascript Method for conversion:

Converting a UUID to a ULID guarantee a unique code. ULIDs are unique 128-bit identifiers that are generated based on a bination of the current timestamp.

function convertUUIDtoULID(uuid) {
  const uuidBinary = uuid.split("-").map((hex) => parseInt(hex, 16));
  return uuidBinary
    .slice(0, 8)
    .map((byte) => byte.toString(32))
    .concat(uuidBinary.slice(8).map((byte) => byte.toString(32)))
    .join("");
}

const uuid = "454391df-b950-42ea-a2c0-92d62c215d67";
const ulid = convertUUIDtoULID(uuid);
console.log(ulid);
  • The function takes in a string of UUID format as an input
  • It splits the UUID string at each "-" character and converts each resulting hexadecimal string into a decimal number using the parseInt() function with base 16.
  • It creates a new array of the first 8 decimal numbers from the UUID and converts each number to a base-32 string using the toString() function with base 32.
  • It concatenates this array with a new array of the remaining decimal numbers from the UUID, also converted to base-32 strings.
  • The resulting array of base-32 strings is joined together into a single string, which is returned as the ULID.
  • The example UUID string is passed into the function and the resulting ULID string is logged to the console.

You can also use [https://www.npmjs./package/ulid] for converting UUID to ULID

发布评论

评论列表(0)

  1. 暂无评论