0

Given this TypeScript snippet:

type URLEx = URL & {
  custom: string;
};

const url = new URL("http://localhost:3000/foo/var");

const url_x: URLEx = {
  ...url,
  custom: "hello",
};

console.log(url);
// {
//   href: 'http://localhost:3000/foo/var',
//   origin: 'http://localhost:3000',
//   protocol: 'http:',
//   username: '',
//   password: '',
//   host: 'localhost:3000',
//   hostname: 'localhost',
//   port: '3000',
//   pathname: '/foo/var',
//   search: '',
//   searchParams: URLSearchParams {},
//   hash: ''
// }

console.log(url_x);
// {
//   custom: 'hello',
//   [Symbol(context)]: URLContext {
//     flags: 400,
//     scheme: 'http:',
//     username: '',
//     password: '',
//     host: 'localhost',
//     port: 3000,
//     path: [ 'foo', 'var' ],
//     query: null,
//     fragment: null
//   },
//   [Symbol(query)]: URLSearchParams {}
// }

I was not expecting to see [Symbol(context)]: URLContext and [Symbol(query)]: URLSearchParams. Why is that so? And how can I convert url into url_x without manually mapping all properties so logging the latter would produce the following?

console.log(url_x);
// {
//   href: 'http://localhost:3000/foo/var',
//   origin: 'http://localhost:3000',
//   protocol: 'http:',
//   username: '',
//   password: '',
//   host: 'localhost:3000',
//   hostname: 'localhost',
//   port: '3000',
//   pathname: '/foo/var',
//   search: '',
//   searchParams: URLSearchParams {},
//   hash: '',
//   custom: 'hello'
// }
danronmoon
  • 3,814
  • 5
  • 34
  • 56
Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214

3 Answers3

1

Extend the URL class:

class URLEx extends URL {
    custom: string
    constructor(url: string) {
        super(url);
        this.custom = 'hello';
    }
}

const urlx = new URLEx(location.href);

Playground example.

See also:

There are three distinct places that accept the spread syntax:

  • Function arguments list (myFunction(a, ...iterableObj, b))
  • Array literals ([1, ...iterableObj, '4', 'five', 6])
  • Object literals ({ ...obj, key: 'value' })
Connor Low
  • 5,900
  • 3
  • 31
  • 52
1

You can't spread the instance of the classes, because classes is a prototype and object spread only Shallow-cloning (excluding prototype) .... Instead, you can do this:

type URLEx = URL & {
  custom: string;
};

const url = new URL("http://localhost:3000/foo/var");
console.log({...url}); // {}

const url_x: URLEx = Object.assign(url, {custom: 'hello'})

console.log(url.host); // "localhost:3000"
console.log(url_x.host); // "localhost:3000"
console.log(url_x.custom); // "hello"
console.log(url === url_x) // true

See TS Playground. Beware of Object.assign() since it'll modify the url value

0

I would suggest composition:

type URLEx = {
  url: URL;
  custom: string;
};

const url = new URL("http://localhost:3000/foo/var");

const url_x: URLEx = {
    url,
    custom: "hello"
};

console.log(url);
console.log(url_x);

output (depends on URL implementation):

[LOG]: URL: "http://localhost:3000/foo/var" 
[LOG]: {
  "url": "http://localhost:3000/foo/var",
  "custom": "hello"
} 
Bearmit
  • 9
  • 2
  • This answer changes the nature of the question. With this, I couldn't use the value as an URL type (which would be valid casting). This could still be a solution, but not what was being asked. – Yanick Rochon Feb 17 '23 at 14:04
  • @YanickRochon A almost agree. I am wondering why inheritance is privileged under composition, the provided example was using console.log, which is fine to pass the wrapper. – Bearmit Feb 21 '23 at 14:02