nitrovatter
nitrovatter

Reputation: 1201

TypeScript - How to define the function interface that accepts both array and object

I want to write the function that sorts both array and objects. My attempt is below.

function sort(obj: any) {
    if (isArray(obj)) {
        // sort as array
    } else if (isObject(obj)) {
        // sort as object
    } else {
        // throw error that primitive value can't be sorted
    }
}

But it lost the typing at all. I tried to use generic parameters but ran into the errors that obj has no array methods. I tried to use function overloading but ran into the impossibility to distinguish array and objects.

Upvotes: 0

Views: 78

Answers (5)

Thomas
Thomas

Reputation: 12637

Typeguards

I suppose you've seen the old link. There was a mistake. I use sort method when obj is array

TS Playground link

Although technically, both Array.isArray and function isArray(value){ return Array.isArray(value); } return a boolean their return types are different.

function isArray(value) { // returns type `boolean`
  return Array.isArray(value); // returns type `value is any[]`
}

function sort<T>(obj: T | T[]) {
    // that's why for TS this is some generic condition, like `obj.id === "42"`
    // it has no idea that `isArray` is supposed to be a typeguard.
    if (isArray(obj)) {
        // so in here it still considers `obj:T | T[]` 
        // and `T` has no `sort()` method.
        obj.sort();
    } else ...
}

same for your other "typeguard" isObject.

And you could alias const isArray = Array.isArray; but basically, why? You don't gain anything here.


Sidenote, your implementation of isObject:

If you want to do if(isObject(value) || isFunction(value)) then do that, but don't "hide" the isFunction check inside something called isObject. It's you who will trip over this eventually. Keep these guards as stupidly simple and straightforward as possible.


An example of how your code could look like TS Playground

const isObject = (value: unknown): value is object => typeof value === "object" && value !== null;

function sort<T extends object>(obj: T): T {
  if (Array.isArray(obj)) {
    obj.sort();

  } else if (isObject(obj)) {
    // leave me alone TS, I know what I'm doing. Things will be fine
    const unsafeObj: any = obj; 

    for (const [key, value] of Object.entries(obj).sort()) {
      delete unsafeObj[key];
      unsafeObj[key] = value;
    }
  }

  return obj;
}


const obj = sort({
  "foo": "foo",
  "bar": "bar",
  "baz": "baz",
  "asdf": "asdf"
});

console.log(obj);

Warnings: Since Array#sort() is mutating the object, I've implemented the object-sort the same way; that doesn't make it good. Mutating things comes with its own set of problems.

And "sorting" objects like that may be fatal to the performace of any code that has to deal with these objects. If you want ordered key-value pairs, check out Maps

Upvotes: 1

Abdelmonaem Shahat
Abdelmonaem Shahat

Reputation: 544

function sorting(args: unknown){
    if(typeof args === 'object' && !Array.isArray(args)){
        // sort as if it is object
    }else{
        // sort as if it array
    }
}

Upvotes: 0

You can use built-in typeguards.


type A = Array<unknown>
type B = Record<string, unknown>

const isObject = (arg: any): arg is B => !Array.isArray(arg) && arg !== null && typeof arg === 'object';

function sort(obj: any) {
    if (Array.isArray(obj)) {
        const check = obj; // A
    } else if (isObject(obj)) {
        const check = obj; //Record<string, unknown>
        // sort as object
    } else {
        const check = obj; // any
    }
}

Keep in mind, Array.isArray is already typeguard.

You can use unio type for obj argument:

function sort(obj: A): A
function sort(obj: B): B
function sort(obj: A | B) {
    if (Array.isArray(obj)) {
        const check = obj; // A

        return obj
    }
    if (isObject(obj)) {
        const check = obj; //Record<string, unknown>
        // sort as object
        return obj
    }

    throw Error('Hello')

}

const x = sort([1, 2]) // A
const y = sort({ a: 1 }) // b
const z = sort(42) // error

Playground link

Upvotes: 0

Ran Turner
Ran Turner

Reputation: 18036

You can specify in your function signature what are the types it's expecting:

function sort(obj: object | [])

Then you can first check if obj is an array by using the Array.isArray method.

Array.isArray(obj)

If it returns true, you can sort it by using the Array sort method. To check whether it's an object you can do by:

typeof obj === 'object'&& !(obj instanceof Array)

If you want to sort an object you can do it using the Object.keys method which returns an array of a given object's own enumerable property and the use sort and reduce.

For example:

Object.keys(obj).sort().reduce(
   (obj, key) => { 
       obj[key] = obj[key]; 
       return obj;
    }, {}
);

Upvotes: 0

Hassan Ghasemi
Hassan Ghasemi

Reputation: 151

You could limit your inputs and test only for array

function sort(input: object | []) {
    if (Array.isArray(input)) {

    } else {

    }
}

this function only accepts object or array. but problem is object is a base type and includes other types.

Upvotes: 0

Related Questions