Как можно преобразовать тип UUID в тип ULID?

Там немного документации, как преобразовать ULID в UUID, но не так много, когда вам нужно преобразовать UUID в ULID.

Я смотрю на этот генератор/конвертер UUID/ULID https://www.ulidtools.com/

но я не совсем уверен, как мне воспроизвести преобразование UUID в ULID, источник слишком запутан, чтобы я мог его понять.

Я не уверен, с чего даже начать, безопасно ли это преобразование? будет ли это гарантировать уникальное преобразование?

4 ответа

У меня была та же проблема, и я взглянул на ulidtools.com, затем, немного покопавшись в его исходном коде, я обнаружил, что он использует этот пакет за видимым.

      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(),
  },
]);

Чистый метод Javascript для преобразования:

Преобразование UUID в ULID гарантирует уникальный код. ULID — это уникальные 128-битные идентификаторы, которые генерируются на основе комбинации текущей метки времени.

      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);
  • Функция принимает строку формата UUID в качестве входных данных.
  • Он разбивает строку UUID на каждый символ «-» и преобразует каждую результирующую шестнадцатеричную строку в десятичное число с помощью функции parseInt() с основанием 16.
  • Он создает новый массив из первых 8 десятичных чисел из UUID и преобразует каждое число в строку с основанием 32 с помощью функции toString() с основанием 32.
  • Он объединяет этот массив с новым массивом оставшихся десятичных чисел из UUID, также преобразованных в строки с основанием 32.
  • Результирующий массив строк с основанием 32 объединяется в одну строку, которая возвращается как ULID.
  • Пример строки UUID передается в функцию, а результирующая строка ULID записывается в консоль.

Вы также можете использовать [https://www.npmjs.com/package/ulid] для преобразования UUID в ULID.

Основываясь на ответе @ImanHosseiniPour, я пришел к следующему:

ВРЕМЕННАЯ МЕТКА + 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(),
});

Имейте в виду, что обратный инжиниринг ULID в UUID приведет к тому, что два первых фрагмента будут отличаться от оригинала, поскольку мы включили временную метку.

Другие ответы на этот вопрос не удовлетворили мои потребности (нет внешних зависимостей). Вот версия, которая работает с ванильным 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.com/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

Вот JSFiddle, если вы хотите проверить функциональность.

Другие вопросы по тегам