I am learning about Typescript union. I assigned an array with type number[] | string[]. But When I am pushing any string or number in it. Then I am getting an error " Argument of type 'string' is not assignable to parameter of type 'never' ".
let arr1: string[] | number[] = [];
let arr2: (string | number)[] = [];
let arr3: Array<number | string> = [];
arr1.push(21);
//Error for above push
arr2.push(1);
arr2.push("John Wick");
arr3.push(2);
arr3.push("John Wick");
Here I want to make arr1 to be either number or string array.
Can someone please help me understand what is happening here.
Is it possible because of union? Cause when I am assigning a new array, then there is no problem. It's only in the case of .push()
let arr1: string[] | number[] = [];
let arr2: (string | number)[] = [];
let arr3: Array<number | string> = [];
arr1 = ["John", "Wick"];
arr1 = [0, 1, 2];
//No Error here
let number: string | number = "true";
number = 21;
Here ...
let arr1: string[] | number[] = [];
... you are basically saying: There is an array arr1 which can either be of type string[] or number[]. At the same time you are assigning an array without any type, so the compiler will not actually know if arr1 is a of type string[] or number[]. As soon as you assign values to the arr1 where the compiler knows the type, it can also correctly check the type when invoking the push operation.
Related
I have a variable with a type that is a union of different array types. I want to narrow its type to a single member of that union, so I can run the appropriate code over it for each type. Using Array.every and a custom type guard seems like the right approach here, but when I try this TypeScript complains that "This expression is not callable." along with an explanation that I don't understand.
Here is my minimum reproducible example:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if (unionArr.every(isNumber)) { // <- Error
unionArr;
}
TypeScript Playground
Here is the error:
This expression is not callable.
Each member of the union type
'{
<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
} | {
...;
}'
has signatures, but none of those signatures are compatible with each other.
This isn't preventing me from continuing. I've found that using a type assertion to recast my array to unknown[] before I narrow its type, as I would do if I were writing an isNumberArray custom type guard, removes the error without compromising type safety.
I've also found that recasting my string[] | number[] array to (string | number)[] removes the error.
However, the type of the array doesn't seem to be narrowed correctly, so I would need to use an additional as number[] after the check:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if ((unionArr as unknown[]).every(isNumber)) { // <- No error
unionArr; // <- Incorrectly typed as string[] | number[]
}
if ((unionArr as (string | number)[]).every(isNumber)) { // <- No error
unionArr; // <- Incrrectly typed as string[] | number[]
}
TypeScript Playground
I tried a comparison with a non-array union as well, though of course in this case I was just using the custom type guard directly instead of using it with Array.every. In that case, there was also no error and the type was narrowed correctly:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const union: string | number = Math.random() > 0.5 ? 1 : '1';
if (isNumber(union)) {
union; // <- Correctly typed as number
}
TypeScript Playground
Because I have that safe type assertion workaround, I can continue without needing to understand this. But I'm still very confused as to why that error appears in the first place, given I am trying to narrow a union of types to a single member of that union.
I'm guessing this is something to do with how Array.every has been typed by TypeScript, and there's probably nothing I can do aside from the workaround I'm already using. But it's hard to be sure of that when I don't really understand what's going wrong. Is there something I could do differently here, or is the as unknown[] type assertion I've used the correct or best way to handle this?
Let's take a simpler example:
type StringOrNumberFn = (a: string | number) => void
type NumberOrBoolFn = (a: number | boolean) => void
declare const justNumber: StringOrNumberFn | NumberOrBoolFn
// works
justNumber(123)
// errors
justNumber('asd')
justNumber(true)
When you have a union of functions that you try to invoke, you are actually calling a function that is the intersection of those members. If you don't know which function it is, then you may only call that function in ways that both functions support. In this case, both functions can take a number, so that's all that's allowed.
And if the intersection of those functions would have incompatible arguments, then the function cannot be called. So let's model that:
type StringFn = (a: string) => void
type NumberFn = (a: number) => void
declare const fnUnion: StringFn | NumberFn
fnUnion(123) // Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
fnUnion('asdf') // Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
This is closer to your problem.
Playground
A string array's every and a number array's every are typed to receive different parameters.
(value: string, index: number, array: string[]) // string[] every() args
(value: number, index: number, array: number[]) // number[] every() args
Which is essentially the same problem as above.
So I don't think there's a way that the compiler will be okay with calling the every method on this union.
Instead, I'd probably write a type assertion for the whole array and loop over it manually.
const isNumberArray = (array: unknown[]): array is number[] => {
for (const value of array) {
if (typeof value !== 'number') return false
}
return true
}
declare const unionArr: string[] | number[]
if (isNumberArray(unionArr)) {
Math.round(unionArr[0]); // works
}
Playground
const str = "abc";
let unicodeArray: Number[] = [];
let code = 0;
for (let i = 0; i < str.length; ++i) {
code = str.charCodeAt(i);//returns number
unicodeArray = unicodeArray.concat([code]);
}
//unicodeArray: [97, 98, 99];
let byteArray = new Uint8Array(unicodeArray);
new Uint8Array() gives the following error message:
No overload matches this call.
The last overload gave the following error.
Argument of type 'Number[]' is not assignable to parameter of type 'ArrayBufferLike'.
Type 'Number[]' is missing the following properties from type 'SharedArrayBuffer': byteLength, [Symbol.species], [Symbol.toStringTag]
See Uint8Array reference at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
var arr = new Uint8Array([21,31]);
May I know what is wrong?
How can I convert the "numbersString" array that has String type values to a "numbersFloat" array with float type values
The problem is that I keep getting "Cannot assign value of type 'String' to subscript of type 'Double'" as an error
I understand that I am not able to input a Float value to a String subscript, but I can't neither change the Strings because they are comma separated and can't put the values in the array
var numbersString = [["564,00", "577,00", "13,00"], ["563,00", "577,00", "14,00"]] → I have
var numbersFloat = [[564.00, 577.00, 13.00], [563.00, 577.00, 14.00]] → I need
Things I have tried:
for row in 0...numbersString.count-1 {
for col in 0...numbersString[0].count-1 {
numbersFloat[row][col] = numbersString[row][col]
}
}
Error: Cannot assign value of type 'String' to subscript of type 'Double'
for row in 0...numbersString.count-1 {
for col in 0...numbersString[0].count-1 {
var a = table[row][col]
table[row][col] = Float(a)
}
}
You can use a NumberFormatter to handle strings containing floats that use commas as their decimal separators. I generally wrap custom formatters in a class. That would look something like this:
class FloatFormatter {
let formatter: NumberFormatter
init() {
formatter = NumberFormatter()
formatter.decimalSeparator = ","
}
func float(from string: String) -> Float? {
formatter.number(from: string)?.floatValue
}
}
Substituting this into your example code (with a fix to the type of your float array) you get:
var numbersString = [["564,00", "577,00", "13,00"], ["563,00", "577,00", "14,00"]]
var numbersFloat: [[Float]] = [[564.00, 577.00, 13.00], [563.00, 577.00, 14.00]]
let floatFormatter = FloatFormatter()
for row in 0...numbersString.count-1 {
for col in 0...numbersString[0].count-1 {
numbersFloat[row][col] = floatFormatter.float(from: numbersString[row][col])!
}
}
This works, but it's not very Swifty. Using map would be better (that way you do not need to worry about matching the sizes of your arrays and pre-allocating the float array).
let floatFormatter = FloatFormatter()
let numbersString = [["564,00", "577,00", "13,00"], ["563,00", "577,00", "14,00"]]
let numbersFloat = numbersString.map { (row: [String]) -> [Float] in
return row.map { stringValue in
guard let floatValue = floatFormatter.float(from: stringValue) else {
fatalError("Failed to convert \(stringValue) to float.")
}
return floatValue
}
}
Maybe you want something like this?
var numbersString = [["564.00", "577.00", "13.00"], ["563.00", "577.00", "14.00"]]
var numbersFloat: [[Float]] = Array(repeating: Array(repeating: 0.0, count: numbersString[0].count), count: numbersString.count)
print(numbersFloat.count)
for row in 0...numbersString.count - 1 {
for col in 0...numbersString[0].count - 1 {
print(row, col, numbersString[row][col])
print(Float(numbersString[row][col]) ?? 0)
numbersFloat[row][col] = Float(numbersString[row][col]) ?? 0
}
}
This will work with both "." and "," separator.
var numbersString = [["564,00", "577,00", "13,00"], ["563,00", "577,00", "14,00"]]
let numberFormatter = NumberFormatter()
let decimalSeparator = ","
numberFormatter.decimalSeparator = decimalSeparator
let numbersFloat = numbersString.map({ $0.compactMap({ numberFormatter.number(from: $0.replacingOccurrences(of: ".", with: decimalSeparator))?.floatValue })})
For everyone: NumberFormatter has .locale property and when formatter is created, locale is set to current device locale. But decimal separators differs in different countries. It can lead to very unpleasant bugs when everything works good for you and even for QAs, but doesn't work for users in production. So it is always best option to control String-to-Double conversion in described way.
I want to add multiple keys to the dictionary via loop. For example, when I create a key, that key already has its value.
I have the following scenario:
var keys = ['key1', 'key2', 'key3', 'key4']
var dic = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
dic[key] = 'value' + i
}
Note that I am adding several keys and already assigning the value of that key, but it does not work in TypeScript, so I have tested it works perfectly in JavaScript.
This line above has error. Note that I cannot add an element that is not contained in the array:
dic[key] = 'value' + i
Error details
Element implicitly has an 'any' type because expression of type
'string' can't be used to index type '{}'.
How can I get my Array keys and assign it to the dictionary with the loop value?
You need to define the type of dic
Use any
var dic: any = {}
Be more precise
var dic: Record<string, string> = {}
Be even more precise
type Key = 'key1' | 'key2' | 'key3' | 'key4'
var keys: Key[] = ['key1', 'key2', 'key3', 'key4']
var dic: Partial<<Record<Key, string>> = {}
If the keys are static, then you can make it even better (credit to this thread)
const keys = ['key1', 'key2', 'key3', 'key4'] as const; // TS3.4 syntax
type Key = typeof keys[number];
var dic: Partial<Record<Key, string>> = {
"key1": "123"
}
I tested in Swift 3.0. I want to add array1 to array2, example and errors as below:
var array1: [String?] = ["good", "bad"]
var array2 = [String!]()
array2.append(array1)
//Cannot convert value of type '[String?]' to expected argument type 'String!'
array2.append(contentsOf: array1)
//Extraneous argument label 'contentsOf:'in call
I know if I change to
var array2 = [String?]()
array2.append(contentsOf: array1)
it works!
How should I fix this if i don't change type?
In Swift 3 you cannot define an array where the generic element is an implicitly unwrapped optional.
Implicitly unwrapped optionals are only allowed at top level and as function results.
The compiler
What you can do is creating a new array of String containing only the populated elements of array1.
let array1: [String?] = ["good", "bad", nil]
let array2: [String] = array1.flatMap { $0 }
print(array2) // ["good", "bad"]
Update
As shown by Sam M this is indeed possible, here's the code
let array2 = array1.map { elm -> String! in
let res: String! = elm
return res
}
var array1: [String?] = ["good", "bad"]
var array2 = [String!]()
var array2a = [String]()
for item in array1 {
array2.append(item)
}
for item in array1 {
array2a.append(item!)
}
print("1", array1)
print("2", array2)
print("2a", array2a)
Prints:
1 [Optional("good"), Optional("bad")]
2 [good, bad]
2a ["good", "bad"]
Mapping also works, e.g.:
array2 = array1.map{ $0 }
array2a = array1.filter{ $0 != nil }.map{ $0! }