How can I create an Eq for an object with an optional property? - fp-ts

I am trying to create an Eq for an object with an optional property. So far I have tried the following:
type Thing = { a: string; b?: string };
const eqThing = Eq.struct<Thing>({
a: S.Eq,
b: S.Eq // Type 'Eq<string>' is not assignable to type 'Eq<string | undefined>'.
});
eqThing.equals({ a: "a", b: "b" }, { a: "a" }); // false
I think that there must be a way to specify that b is Eq<string | undefined> but I'm not sure how.

This can be achieved by using Eq.eqStrict.
type Thing = { a: string; b?: string };
const partialStruct = Eq.struct<Thing>({
a: S.Eq,
b: Eq.eqStrict
});
expect(partialStruct.equals({ a: "a", b: "b" }, { a: "a" })).toBe(false);
expect(partialStruct.equals({ a: "a", b: "b" }, { a: "a", b: "b" })).toBe(true);

Related

Typescript: problem with intersection of types containing a subset of specialized arrays

I've a problem with intersection of types containing array members whose type is a subset of a specific type. It's harder to explain for me than simply show you the code.
This is a custom type
type MyType = 'A' | 'B' | 'C' | 'D';
This is the generic interface for the "base" object
interface Base<A = {}, B = {}> {
stringMember: string;
arrayMember: MyType[];
objectMember: {
something: boolean;
} & A;
b: B;
}
I need to specialize arrayMember, objectMember and b memebers like so
type A_Object_1 = {
somethingElse: number;
};
type B_Object_1 = {
aMember: boolean;
};
// Specialized type 1
type Specialized_1 = Base<A_Object_1, B_Object_1> & {
arrayMember: ['A'];
};
// Specialized type 1 object, this is OK
const a_OK: Specialized_1 = {
stringMember: 'hi',
arrayMember: ['A'],
objectMember: {something: true, somethingElse: 3},
b: {aMember: true},
};
and so
type A_Object_2 = {
anything: boolean;
};
type B_Object_2 = {
bMember: number;
};
type Specialized_2 = Base<A_Object_2, B_Object_2> & {
arrayMember: ['B'];
};
// Specialized type 2 object, this is OK
const b_OK: Specialized_2 = {
stringMember: 'hello',
arrayMember: ['B'],
objectMember: {something: true, anything: true},
b: {bMember: 3},
};
I need an intersection type between Specialized_1 and Specialized_2, it should be like this
{
stringMember: string;
arrayMember: ['A', 'B'];
objectMember: {
something: boolean;
somethingElse: number;
anything: boolean;
};
b: {aMember: boolean; bMember: number};
}
I do that the following (maybe wrong) way
type Specialized_3 = Specialized_1 & Specialized_2;
The members that are objects are ok, since they contain both members from Specialized_1 & Specialized_2
Anyway there is a problem with the array type that is never, I see why, there is no intersection between ['A'] and ['B'],
const c_Problem: Specialized_3 = {
stringMember: 'hi there',
arrayMember: ['A', 'B'], // Error here because arrayMember: never
objectMember: {something: true, somethingElse: 3, anything: false}, // OK
b: {aMember: false, bMember: 8}, // OK
};
It's been 2 days I'm thinking about that and I can't figure it out...
Moreover I find difficult to find the search keywords for this very specific problem.
Any help?
EDIT:
Thanks to #AlekseyL.I made some progress, I can use Omit to omit arrayMember from the intersected object and than union it later "manually"
I can do this using a generic type like so:
type PossibleSolution<T1 extends Base, T2 extends Base> = Omit<T1 & T2, 'arrayMember'> & {
arrayMember: [T1['arrayMember'][number], T2['arrayMember'][number]];
};
type Specialized_Test = PossibleSolution<Specialized_1, Specialized_2>;
const d_OK: Specialized_Test = {
stringMember: 'hi there',
arrayMember: ['A', 'B'], // OK
objectMember: {something: true, somethingElse: 3, anything: false}, // OK
b: {aMember: false, bMember: 8}, // OK
};
The use of the generic type is for convenience since I need to reuse that here and there in my code.
This solution works, but only to intersect 2 objects, if I need to intersect 3 objects I have to delcare a different generic type
Now the problem is, how can I modify PossibileSolution generic type to intersect N SpecializedObjects?

How to iterate over known property keys in TypeScript?

This is my code:
interface Siswa {
name: string,
points: { a: number, b: number, c: number, d?: number, e?: number }
}
function find_average_points(data: Array<Siswa>): Array<{name: string, average: number}> {
let returned_array: Array<{name: string, average: number}> = [];
data.forEach((item: Siswa) => {
let sum: number = 0;
let keys = Object.keys(item.points);
keys.forEach((key: any) => sum+=item.points[key]);
returned_array.push({name: item.name, average: (sum/keys.length)});
});
return returned_array;
}
When I tried this function in JavaScript, it ran correctly and the result is what I want, but in TypeScript, I got an error in item.points[key]. It says:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; b: number; c: number; d?: number | undefined; e?: number | undefined; }'.
No index signature with a parameter of type 'string' was found on type '{ a: number; b: number; c: number; d?: number | undefined; e?: number | undefined; }'
I don't know what it means.
Using Object.keys() returns the type string[] (as explained in this comment). Instead, you can create arrays for the known keys in the points object, use them to build your types, and then loop through those keys when summing the values:
TS Playground link
const definitePoints = ['a', 'b', 'c'] as const;
const maybePoints = ['d', 'e'] as const;
type Points = (
Record<typeof definitePoints[number], number>
& Partial<Record<typeof maybePoints[number], number>>
);
interface Siswa {
name: string,
points: Points;
}
interface SiswaAverage {
name: string;
average: number;
}
function find_average_points (data: Array<Siswa>): Array<SiswaAverage> {
const result: Array<SiswaAverage> = [];
for (const {name, points} of data) {
let sum = 0;
let count = 0;
for (const point of [...definitePoints, ...maybePoints]) {
const value = points[point];
if (typeof value !== 'number') continue;
sum += value;
count += 1;
}
result.push({name, average: sum / count});
}
return result;
}
You can use a combination of keyof and null check to solve the problem.
interface Points { a: number, b: number, c: number, d?: number, e?: number } // only did to use keyof
interface Siswa {
name: string,
points: Points
}
function find_average_points(data: Array<Siswa>): Array<{name: string, average: number}> {
let returned_array: Array<{name: string, average: number}> = [];
data.forEach((item: Siswa) => {
let sum: number = 0;
let keys = Object.keys(item.points) as (keyof Points)[]; // use this if you know you wont add extra non number properties in Point
keys.forEach((key) => sum+=item.points[key] ?? 0); // for d and e with are optional
returned_array.push({name: item.name, average: (sum/keys.length)});
});
return returned_array;
}
let obj: Siswa = { name: 'Paris', points: { a: 2 , b:2, c: 4, d: 4 }} // can not add random stuff type safe
console.log(find_average_points([obj]))

How to combine two objects of different types in TypeScript?

Initially, I define
a = {};
b:any;
After some processing i get
a = {p:"ram", f:"sita"};
b = {"gita","mita"};
I tried to merge these two results. I tried
cart = [];
cart.push(this.a);
cart.push(this.b);
console.log(this.cart);
and get the result as below:
{p: "ram", f: "sita"}
{b: ["gita","mita"]}
I want the result like:
a ={p:"ram", f:"sita", b:"gita","mita"}
How can I achieve the above.
The example you posted isn't valid TypeScript nor JavaScript, but I assume you meant something like this:
a = { p: "1", f: "2" };
b = [ "3", "5" ];
output = { p: "1", f: "2", b: [ "3", "5" ] };
In which case, you don't need to do anything special: TypeScript fully understands what Object.assign does:
interface AWithB {
readonly a: { readonly p: string, readonly f: string };
readonly b: readonly string[];
}
const a = { p: "1", f: "2" };
const b = [ "3", "5" ];
const output = Object.assign( {}, a, { b: b } );
function requiresAWithB( arg: AWithB ): void {
console.log( arg );
}
requiresAWithB( output ); // <-- no error, because `output` matches `interface AWithB`.

How to update Json Data on same key in AngularJS?

var mainObject = {
a: "a",
b: "b",
c: "c",
d: "d"
}
var testObject = {
a: "",
b: ""
}
I want result like:
var testObject = {
a: "a",
b: "b"
}
you can achieve this using for loop and hasOwnProperty function
var mainObject = {
a: "a",
b: "b",
c: "c",
d: "d"
}
var testObject = {
a: "",
b: ""
}
for(keyOne in testObject){
if(mainObject.hasOwnProperty(keyOne)){
testObject[keyOne]= mainObject[keyOne]
}
}
console.log(testObject)
Is this what you are looking for...
var mainObject = {
a: "a",
b: "b",
c: "c",
d: "d"
}
var testObject = {
a: "",
b: ""
}
for(var ob in mainObject){
if(Object.keys(testObject).indexOf(ob)!=-1)
testObject[ob] = mainObject[ob]
}
console.log(testObject)

Sort array in Swift using a variable property?

I have an NSTableView with an array datasource that I'm sorting when the user clicks on a column header. At the moment I have some IF statements to sort ascending/descending for every column name. Is it possible to perform a sort using a variable property name? That is, instead of hard coding .name in the following example, I could use a variable containing the name of the property instead of .name?
tableContents.sort {
$0.name.localizedCaseInsensitiveCompare($1.name) == NSComparisonResult.OrderedAscending
}
This does work, but I have to duplicate this for every column in the table
If all the properties that are used for sorting have the same type, you could use the built-in key-value access. Consider the following class A with two properties a and b both of which are strings.
class A: NSObject, Printable {
var a: String
var b: String
init(a: String, b: String) {
self.a = a
self.b = b
}
override var description: String {
return "{ a: \(self.a), b: \(self.b) }"
}
}
Let's say your table contents are build from instances of this class.
var tableContents = [A(a: "1", b: "2"), A(a: "2", b: "1")]
To sort this array based on the values of either a or b you could use the following.
tableContents.sort {
let key = "a"
let oneValue = $0.valueForKey(key) as! String
let otherValue = $1.valueForKey(key) as! String
return oneValue.localizedCaseInsensitiveCompare(otherValue) == NSComparisonResult.OrderedAscending
}
println(tableContents) // [{ a: 1, b: 2 }, { a: 2, b: 1 }]
tableContents.sort {
let key = "b"
let oneValue = $0.valueForKey(key) as! String
let otherValue = $1.valueForKey(key) as! String
return oneValue.localizedCaseInsensitiveCompare(otherValue) == NSComparisonResult.OrderedAscending
}
println(tableContents) // [{ a: 2, b: 1 }, { a: 1, b: 2 }]
The only difference in the comparison block is the usage of either let key = "a" or let key = "b".

Resources