how to type Record classes and instances? - immutable.js

I understand that Record creates a new class. Why then does the following typecheck in Flow:
const Person = Record({fname : null, lname: null});
const Person2 = Record({fnameMANGLED: null, lname: null});
const p : Person2 = new Person({fname: 'joe', lname: 'doe'}); // shouldn't Flow complain?
In contrast, when using ordinary classes, the following doesn't typecheck (as expected):
class A{constructor() {}};
class B{constructor() {}};
const a: A = new A();

As of v4.0.0-rc.5 they added types RecordOf<T> and RecordFactory<T>.
Here is a snippet from the docs on how to use these flow types.
import type { RecordFactory, RecordOf } from 'immutable';
// Use RecordFactory<TProps> for defining new Record factory functions.
type Point3DProps = { x: number, y: number, z: number };
const makePoint3D: RecordFactory<Point3DProps> = Record({ x: 0, y: 0, z: 0 });
export makePoint3D;
// Use RecordOf<T> for defining new instances of that Record.
export type Point3D = RecordOf<Point3DProps>;
const some3DPoint: Point3D = makePoint3D({ x: 10, y: 20, z: 30 });

It's because in the current type definitions of immutable JS, Record factory constructor returns any.
If you uncomment the line /*T & Record<T>*/ it catches the error as expected.

Related

Typescript nested dynamic properties

I've been trying to improve the types of an older project that uses a lot of 'any' whenever things get complicated.
Replit-link
Consider the following irregular data structure where Data is an interface matching the example (some are nested objects, some are not. I use the same mapping function on all pages, depending on what is present in localData):
const data: Data = {
car: { name: 'x', speed: 45},
cat: { fur: true },
random: ['hi', 'bye']
why: "because"
};
Now I'm mapping this data on different pages like so
const nestedKey = 'car';
const localData = {
name: '',
speed: 0
};
Object.keys(localData).forEach(key => {
if (nestedKey && data[nestedKey]) {
// Here I'm not too sure what type to give key to make TS happy
localData[key] = data[nestedKey as keyof Data][key]
} else {
localData[key] = data[key as keyof Data]
}
});
const nestedKey: keyof typeof data = 'car';
keyof typeof data will return this literal type:
'car' | 'cat' | 'random' | 'why'
Which you could consume it like:
localData[key] = data[nestedKey][key]

detect an enum at runtime and stringify as keys

playground
I have a bunch of interfaces, at least 2-3 levels nested, where some of the leafs are numbers/strings, etc, but others are (numeric) enums.
I don't want to change this.
Now I want to "serialize" objects that implements my interfaces as JSON. Using JSON.stringify is good for almost all cases, but the enums, that are serialized with their (numerical) value.
I know that it's possible to pass a replacer function to JSON.stringify, but I'm stuck, as I'm not sure how to write a function that detect the structure of my object and replace the enum values with the appropriate names.
example:
enum E { X = 0, Y = 1, Z = 2 }
enum D { ALPHA = 1, BETA = 2, GAMMA = 3 }
interface C { e: E; }
interface B { c?: C; d?: D; }
interface A { b?: B; }
function replacer(this: any, key: string, value: any): any {
return value;
}
function stringify(obj: A): string {
return JSON.stringify(obj, replacer);
}
const expected = '{"b":{"c":{"e":"Y"},"d":"ALPHA"}}';
const recieved = stringify({ b: { c: { e: E.Y }, d: D.ALPHA } });
console.log(expected);
console.log(recieved);
console.log(expected === recieved);
It's not possible to automatically find out which enum was assigned to a field, not even with typescript's emitDecoratorMetadata option. That option can only tell you it's a Number and it will only be emitted on class fields that have other decorators on them.
The best solution you have is to manually add you own metadata. You can do that using reflect-metadata node module.
You'd have to find all enum fields on all of your classes and add metadata saying which enum should be used for serializing that field.
import 'reflect-metadata';
enum E
{
ALPHA = 1,
BETA = 2,
GAMMA = 3,
}
class C
{
// flag what to transform during serialization
#Reflect.metadata('serialization:type', E)
enumField: E;
// the rest will not be affected
number: number;
text: string;
}
This metadata could be added automatically if you can write an additonal step for your compiler, but that is not simple to do.
Then in your replacer you'll be able to check if the field was flagged with this matadata and if it is then you can replace the numeric value with the enum key.
const c = new C();
c.enumField= E.ALPHA;
c.number = 1;
c.text = 'Lorem ipsum';
function replacer(this: any, key: string, value: any): any
{
const enumForSerialization = Reflect.getMetadata('serialization:type', this, key);
return enumForSerialization ? enumForSerialization[value] ?? value : value;
}
function stringify(obj: any)
{
return JSON.stringify(obj, replacer);
}
console.log(stringify(c)); // {"enumField":"ALPHA","number":1,"text":"Lorem ipsum"}
This only works with classes, so you will have to replace your interfaces with classes and replace your plain objects with class instances, otherwise it will not be possible for you to know which interface/class the object represents.
If that is not possible for you then I have a much less reliable solution.
You still need to list all of the enum types for all of the fields of all of your interfaces.
This part could be automated by parsing your typescript source code and extracting the enum types for those enum fields and then saving it in a json file that you can load in runtime.
Then in the replacer you can guess the interface of an object by checking what are all of the fields on the this object and if they match an interface then you can apply enum types that you have listed for that interface.
Did you want something like this? It was the best I could think without using any reflection.
enum E { X = 0, Y = 1, Z = 2 }
enum D { ALPHA = 1, BETA = 2, GAMMA = 3 }
interface C { e: E; }
interface B { c?: C; d?: D; }
interface A { b?: B; }
function replacer(this: any, key: string, value: any): any {
switch(key) {
case 'e':
return E[value];
case 'd':
return D[value];
default:
return value;
}
}
function stringify(obj: A): string {
return JSON.stringify(obj, replacer);
}
const expected = '{"b":{"c":{"e":"Y"},"d":"ALPHA"}}';
const recieved = stringify({ b: { c: { e: E.Y }, d: D.ALPHA } });
console.log(expected);
console.log(recieved);
console.log(expected === recieved);
This solution assumes you know the structure of the object, just as you gave in the example.

json.stringify return value

I'm trying to get datatypes of a JSON.stringify to compare them.
I had used:
var id = d.patogeno;
alert(id);
But it tell the alert that is not defined.
I got this database:
var IDData = JSON.stringify([
["node/9837102", "node/26794", "Customer", "patongenoA", "1412451.0", 3, 520, "1412381"],
["node/9837102", "node/44210", "Customer", "patongenoB", "1436765.0", 2, 384, "1436693"],
]);
and the following function:
function createNodes(startnodes, endnodes, startnodetype, endnodetype, PayTime, TXN_COUNT, Total_Amt, SendTime) {
var node_set = new Set();
var links = [];
var nodetype = d3.set();
startnodes.forEach(function(src, i) {
var tgt = endnodes[i];
node_set.add({
id: src,
type: startnodetype[i]
});
node_set.add({
id: tgt,
type: endnodetype[i]
});
links.push({
source: src,
target: tgt,
paytime: PayTime[i],
patogeno: TXN_COUNT[i], // cambio ---- variable con cual trabajar
total_amt: Total_Amt[i],
SendTime: SendTime[i],
value: 1
});
});
I need to know the value of every patogeno and return it to compare it. Is there a way to do it?, or How to return a specific value, for example: "patogenoB" from IDData?
First, JSON.stringify produces a string.
const obj = {example: 5}
const str = JSON.stringify(obj)
console.log(typeof str) // 'string'
If you want an addressable object, you would do the reverse to a string:
const str = '{"example": 5}'
const obj = JSON.parse(str)
console.log(obj.example) // 5
I do not know how to answer your question with respect to JSON.stringify, nor do I understand why you would be turning an array of arrays into a string instead of converting them into addressable objects, but at the very end of createNodes, you would be able to do this:
links.forEach((d) => console.log(d.patogeno))

get specific keys with value of a object in new object [duplicate]

How one can write a function, which takes only few attributes in most-compact way in ES6?
I've came up with solution using destructuring + simplified object literal, but I don't like that list of fields is repeated in the code.
Is there an even slimmer solution?
(v) => {
let { id, title } = v;
return { id, title };
}
Here's something slimmer, although it doesn't avoid repeating the list of fields. It uses "parameter destructuring" to avoid the need for the v parameter.
({id, title}) => ({id, title})
(See a runnable example in this other answer).
#EthanBrown's solution is more general. Here is a more idiomatic version of it which uses Object.assign, and computed properties (the [p] part):
function pick(o, ...props) {
return Object.assign({}, ...props.map(prop => ({[prop]: o[prop]})));
}
If we want to preserve the properties' attributes, such as configurable and getters and setters, while also omitting non-enumerable properties, then:
function pick(o, ...props) {
var has = p => o.propertyIsEnumerable(p),
get = p => Object.getOwnPropertyDescriptor(o, p);
return Object.defineProperties({},
Object.assign({}, ...props
.filter(prop => has(prop))
.map(prop => ({prop: get(props)})))
);
}
I don't think there's any way to make it much more compact than your answer (or torazburo's), but essentially what you're trying to do is emulate Underscore's pick operation. It would be easy enough to re-implement that in ES6:
function pick(o, ...fields) {
return fields.reduce((a, x) => {
if(o.hasOwnProperty(x)) a[x] = o[x];
return a;
}, {});
}
Then you have a handy re-usable function:
var stuff = { name: 'Thing', color: 'blue', age: 17 };
var picked = pick(stuff, 'name', 'age');
The trick to solving this as a one-liner is to flip the approach taken: Instead of starting from original object orig, one can start from the keys they want to extract.
Using Array#reduce one can then store each needed key on the empty object which is passed in as the initialValue for said function.
Like so:
const orig = {
id: 123456789,
name: 'test',
description: '…',
url: 'https://…',
};
const filtered = ['id', 'name'].reduce((result, key) => { result[key] = orig[key]; return result; }, {});
console.log(filtered); // Object {id: 123456789, name: "test"}
alternatively...
const filtered = ['id', 'name'].reduce((result, key) => ({
...result,
[key]: orig[key]
}), {});
console.log(filtered); // Object {id: 123456789, name: "test"}
A tiny bit shorter solution using the comma operator:
const pick = (O, ...K) => K.reduce((o, k) => (o[k]=O[k], o), {})
console.log(
pick({ name: 'John', age: 29, height: 198 }, 'name', 'age')
)
ES6 was the latest spec at the time when the question was written. As explained in this answer, key picking is significantly shorter in ES2019 than in ES6:
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => ['foo', 'bar'].includes(key))
)
TC39's object rest/spread properties proposal will make this pretty slick:
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
z; // { a: 3, b: 4 }
(It does have the downside of creating the x and y variables which you may not need.)
You can use object destructuring to unpack properties from the existing object and assign them to variables with different names - fields of a new, initially empty object.
const person = {
fname: 'tom',
lname: 'jerry',
aage: 100,
}
let newPerson = {};
({fname: newPerson.fname, lname: newPerson.lname} = person);
console.log(newPerson);
There's currently a strawman proposal for improving JavaScript's object shorthand syntax, which would enable "picking" of named properties without repetition:
const source = {id: "68646", genre: "crime", title: "Scarface"};
const target = {};
Object.assign(target, {source.title, source.id});
console.log(picked);
// {id: "68646", title: "Scarface"}
Unfortunately, the proposal doesn't seem to be going anywhere any time soon. Last edited in July 2017 and still a draft at Stage 0, suggesting the author may have ditched or forgotten about it.
ES5 and earlier (non-strict mode)
The concisest possible shorthand I can think of involves an ancient language feature nobody uses anymore:
Object.assign(target, {...(o => {
with(o) return { id, title };
})(source)});
with statements are forbidden in strict mode, making this approach useless for 99.999% of modern JavaScript. Bit of a shame, because this is the only halfway-decent use I've found for the with feature. 😀
I have similar to Ethan Brown's solution, but even shorter - pick function. Another function pick2 is a bit longer (and slower), but allows to rename properties in the similar to ES6 manner.
const pick = (o, ...props) => props.reduce((r, p) => p in o ? {...r, [p]: o[p]} : r, {})
const pick2 = (o, ...props) => props.reduce((r, expr) => {
const [p, np] = expr.split(":").map( e => e.trim() )
return p in o ? {...r, [np || p]: o[p]} : r
}, {})
Here is the usage example:
const d = { a: "1", c: "2" }
console.log(pick(d, "a", "b", "c")) // -> { a: "1", c: "2" }
console.log(pick2(d, "a: x", "b: y", "c")) // -> { x: "1", c: "2" }
I required this sollution but I didn't knew if the proposed keys were available. So, I took #torazaburo answer and improved for my use case:
function pick(o, ...props) {
return Object.assign({}, ...props.map(prop => {
if (o[prop]) return {[prop]: o[prop]};
}));
}
// Example:
var person = { name: 'John', age: 29 };
var myObj = pick(person, 'name', 'sex'); // { name: 'John' }
Some great solutions above, didn't see one for Typescript fleshed out, so here it goes. Based on #Ethan Browns solution above
const pick = < T extends object, K extends keyof T >(
obj: T,
...keys: K[]
): Pick< T, K > =>
keys.reduce< any >( ( r, key ) => {
r[ key ] = obj[ key ];
return r;
}, {} );
And for bonus, here is TS friendly es6 omit, and one that is much more performant below, but less es6.
const omit = < T extends object, K extends keyof T >(
obj: T,
...keys: K[]
): Omit< T, K > =>
keys.reduce( ( r, key ) => ( delete r[ key ], r ), {
...obj,
} );
Way more performant omit: http://jsben.ch/g6QCK
const omit = < T extends object, K extends keyof T >(
obj: T,
...keys: K[]
): Omit< T, K > => {
let r: any = {};
let length = keys.length;
while ( length-- ) {
const key = keys[ length ];
r[ key ] = obj[ key ];
}
return r;
};
inspired by the reduce approach of https://stackoverflow.com/users/865693/shesek:
const pick = (orig, keys) => keys.reduce((acc, key) => ({...acc, [key]: orig[key]}), {})
or even slightly shorter using the comma operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator)
const pick = (obj, keys) => keys.reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
usage:
pick({ model : 'F40', manufacturer: 'Ferrari', productionYear: 1987 }, 'model', 'productionYear')
results in:
{model: "F40", productionYear: 1987}

TypeScript function type syntax explanation

I'm reading the TypeScript Handbook and in the Functions section under the Function Types heading there is this example (which I understand):
let myAdd = function(x: number, y: number): number { return x+y; };
This is followed by
let’s write the full type of the function out by looking at the each
piece of the function type.
and this syntax:
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x+y; };
Could someone break this down and explain it as I have never seen this before and can't find an explanation in the handbook?
This line:
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x+y; };
Consists of 3 parts:
(1) The variable declaration, this part is the let myAdd. I assume that there's nothing to add here, it's just like with js.
(2) The type of the variable: (x: number, y: number) => number.
Here we're defining a type of a function that expects two parameters, both of type number, named x and y.
The function needs to return a number.
(3) The assignment of the value to the variable: = function(x: number, y: number): number { return x+y; }.
This is just like javascript as well, except for the added types for the params and return value.
If you look at it you'll see that the actual implementation matches the declared type perfectly.
You can also write it like this:
let myAdd: (x: number, y: number) => number = function(x, y) { return x+y; };
Or:
let myAdd: (x: number, y: number) => number = (x, y) => { return x+y; };
The first line:
let myAdd: (x: number, y: number) => number
Is declaring the type of the variable "myAdd". Typescript can automatically infer this in the first example, alternatively (which the second example shows) you can implicitly tell Typescript what it should expect.
The second line:
function(x: number, y: number): number { return x+y; };
Refers to the type of the function itself which you have assigned to the variable "myAdd".
For a simpler example illustrating the same thing:
let myString: (input: string) => string = (input: string) => input;
Alternatively a different example of implicitly declaring the variables type:
let myNumber: number = 10;
Both of the above tell Typescript what the variable should be.