puleos / object-hash

Generate hashes from javascript objects in node and the browser.
MIT License
1.41k stars 145 forks source link

How to import? #150

Closed Ghostbird closed 6 months ago

Ghostbird commented 6 months ago

I'm using object-hash in an Angular 17 project, but when I need to pass the options I don't know how to correctly import it. It seems the module exports the main function in a way that conflicts with the namespace export.

I'm using the hash here to check whether two objects are equivalent. However these objects may contain thumbnails. I don't care about differences in thumbnails, and they slow down hashing.

import * as objectHash from 'object-hash';
import { NotUndefined } from 'object-hash';

const noThumbnailHash = (obj: NotUndefined) =>
  objectHash(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });

If I do this, I get a compile time warning:

▲ [WARNING] Calling "objectHash" will crash at run-time because it's an import namespace object, not a function [call-import-namespace]

And at runtime the promised crash:

ERROR TypeError: objectHash is not a function
    at noThumbnailHash…
Ghostbird commented 6 months ago

I thought this might work, but it doesn't:

import * as objectHash from 'object-hash';
type NotUndefined = objectHash.NotUndefined;

const noThumbnailHash = (obj: NotUndefined) =>
  objectHash(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });

This doesn't work either:

import * as objectHashFn from 'object-hash';
import * as objectHashNamespace from 'object-hash';
type NotUndefined = objectHashNamespace.NotUndefined;

const noThumbnailHash = (obj: NotUndefined) =>
  objectHashFn(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });
Ghostbird commented 6 months ago

Actually even this doesn't work

import * as objectHash from 'object-hash';
type NotUndefined = object | string | number | boolean | null | NotUndefined[];

const noThumbnailHash = (obj: NotUndefined) =>
  objectHash(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });
Ghostbird commented 6 months ago

~Ok, I found a way to get it to work:~

/** utils/hash.ts */
import * as objectHash from 'object-hash';
export const hashFn: Function = objectHash;
/** Original code, but the objectHash function is now imported throught the utils/hash wrapper */
import { hashFn } from 'utils/hash';
import { NotUndefined } from 'object-hash';

const noThumbnailHash = (obj: NotUndefined) =>
  hashFn(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });
Ghostbird commented 6 months ago

Ah, that doesn't work in the end. The typing isn't correct enough when I do that. Function is not the right type.

Ghostbird commented 6 months ago

I found a workaround that seems to work, it requires two intermediate files. One to unambiguously re-export the necessary parts of the objectHash namespace, another to do the same for the function objectHash. This seems to work and now it always uses the correct object at runtime.

/** utils/types.ts */
export { NotUndefined, NormalOption } from 'object-hash';
/** utils/hash.ts */
import * as objectHash from 'object-hash';
import { NotUndefined, NormalOption } from 'utils/types';

export const hashFn: (
  object: NotUndefined,
  options?: NormalOption,
) => string = objectHash;
/** Original code, but the objectHash function is now imported through the utils/hash wrapper */
import { hashFn } from 'utils/hash';
import { NotUndefined } from 'utils/types';

const noThumbnailHash = (obj: NotUndefined) =>
  hashFn(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });
Ghostbird commented 6 months ago

Ah, too bad. Turns out that in production this still goes wrong. In some cases the value used is the namespace, instead of the function, and the application stops working.

Ghostbird commented 6 months ago

I think I've got it working now.

import objectHash, { NotUndefined } from 'object-hash';

const noThumbnailHash = (obj: NotUndefined) =>
  hashFn(obj, {
    excludeKeys: (key: string) => key === 'thumbnail',
  });

But to make this work, I first needed to change the typescript compiler options and set both esModuleInterop and isolatedModules. This also seems to work in all production builds.