0

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.

6
  • Do you mean function sort<T>(obj: T | T[])? Or if you know the type already replace T bei the type. Commented Feb 20, 2021 at 11:02
  • I don't know the type. When I write the interface above I can't call obj.sort(..) because it produces error: property sort does not exists on type 'T | T[]'. Of course I can use type assertion, but it looks like workaround: (obj as T[]).sort(..) Commented Feb 20, 2021 at 11:08
  • @nitrovatter something doesn't add up. The function above is not supposed to be called as a method obj.sort(). The call should look like this: sort(obj). For the same reason, the error property sort does not exists on type 'T | T[]' doesn't make sense, it doesn't match the interface @ThomasRegner provided. Could you please clarify what exactly the interface of your function is and how you call it. Commented Feb 20, 2021 at 11:37
  • @Thomas, try it typescriptlang.org/play?#code/… Commented Feb 20, 2021 at 11:57
  • OK, you tried to call obj.sort() inside your sort function. Arrays have a sort()-method, other Objects don't. You will have to implement it yourself. Commented Feb 20, 2021 at 12:01

5 Answers 5

1

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

Sign up to request clarification or add additional context in comments.

1 Comment

The wonderful answer. Thank you very much.
0

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.

1 Comment

The type [] is equivalent to never[]. I think you meant any[] or unknown[]. But why? object already includes any kind of arrays. The only improvement I would make is: function sort<T extends object>(input: T): T {} so that TS detects the input type and uses it for the return type.
0

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

2 Comments

It doesn't solve the problem. I want to create the function that sorts arrays and objects simultaneously.
@ran.t Object.fromEntries(Object.entries(obj).sort())
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

Comments

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

2 Comments

!Array.isArray(args) is pointless, if that were true it would have entered the first if and never the else if.
you are right, I totally agreed. I will edit it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.