Using the Decoder API, is there a way to define a decoder with mutually exclusive properties?
import * as D from 'io-ts/Decoder';
const decoder = pipe(
D.struct({
a: D.string
}),
D.intersect(
D.partial({
b: D.string,
c: D.boolean
})
)
);
The above sets up a situation where b and c can both be present, but are optional. How could I instead require that one of b or c must be present, but not both?
You can use the Union combinator.
const decoder = pipe(
D.struct({
a: D.string
}),
D.intersect(
D.union(
D.struct({ b: D.string }),
D.struct({ c: D.string })
)
)
);
Bear in mind that you won't be able to access b or c without first checking if those properties exist, since Typescript doesn't have a way to know which one of the two is present in your object.
type Decoder = D.TypeOf<typeof decoder>
declare const myDecoder: Decoder;
myDecoder.a // inferred as `string`
myDecoder.b // TYPE ERROR: Property 'b' does not exist on type '{ a: string; } & { c: string; }'
myDecoder.c // TYPE ERROR: Property 'c' does not exist on type '{ a: string; } & { b: string; }'
if ("b" in myDecoder) {
myDecoder.b // inferred as `string`
}
if ("c" in myDecoder) {
myDecoder.c // inferred as `string`
}
Notice how, when checking for both the mutually exclusive properties, you get a type error. TypeScript correctly infers that it's a case that can never happen (myDecoder is inferred as never inside the if block)
if ("b" in myDecoder && "c" in myDecoder) {
myDecoder.b // TYPE ERROR: Property 'b' does not exist on type 'never'.
myDecoder.c // TYPE ERROR: Property 'c' does not exist on type 'never'.
}
Related
I have this code snippet in many places of my project. This worked without any issue until I upgrade all my packages including react-scripts typescript and react-i18next.
import _get from 'lodash/get';
const { translate } = useLocales();
translate(
_get(error, 'response.data.validationFailures.0.field') ,
_get(error, 'response.data.validationFailures.0.code')
),
The error that I am getting is
Argument of type 'TFunctionDetailedResult' is not assignable
to parameter of type 'SnackbarMessage'.
Making _get(error, 'response.data.validationFailures.0.field') as String won't solve the problem either.
But I have a similar problem before,
.required(translate('validations.organization.name', 'Organization name is required.'))
Here I got this error message
Argument of type 'DefaultTFuncReturn' is not assignable to parameter
of type 'Message<{}> | undefined'.
I have solved the error by adding this file to the project
i18next.d.ts
import 'i18next';
declare module 'i18next' {
interface CustomTypeOptions {
returnNull: false;
}
}
The issue was validations.organization.name would return null. So after overriding that this is not returning null, the issue was fixed. Hope if I declare that the _get method always returns a string the error would fix. How do I do that?
Also _get method won't return a string always. It return what's whats in the path. So the override won't be a global override as well.
The TFunctionDetailedResult type is one of the possible return types from the i18next.t translation function. i18next.t can return a few different types depending on what options you provide. Specifically, the option that we are concerned with here is returnDetails.
If you call t with { returnDetails: true } in the options then you will get back a result object with type TFunctionDetailedResult.
export type TFunctionDetailedResult<T = string> = {
/**
* The plain used key
*/
usedKey: string;
/**
* The translation result.
*/
res: T;
/**
* The key with context / plural
*/
exactUsedKey: string;
/**
* The used language for this translation.
*/
usedLng: string;
/**
* The used namespace for this translation.
*/
usedNS: string;
};
As you can see, the .res property contains the actual result of the translation. So you can call your snackbar with result.res as the message.
You can get back just a string if you call t with the option { returnDetails: false } or if you don't specify this option at all, as false is the default value.
In your code, you are expecting a string type but getting a TFunctionDetailedResult type. It is unclear to me if this is a TypeScript error, and your result is actually a string, or if the TypeScript types are correct and you are getting this results object.
If it is a TypeScript error (your variable is actually a string but it is has the type TFunctionDetailedResult):
Make sure that the translate function returned from your useLocales hook has the type (...keys: string[]): string.
If it is an actual error (your variable is a TFunctionDetailedResult object):
You can keep this as an object and use the .res property of the object for your snackbar.
You can modify the options on your t call so that you get a string result instead.
Your issues with the lodash _get types seem secondary to me. It did play with this a bit and defined an accurate type for the error variable.
interface ErrorType {
response: {
data: {
validationFailures: Array<{
field: string;
code: string; // or is it a number?
}>
}
}
}
Oddly, this gives me the correct type for _get(error, 'response.data.validationFailures.0') but not for the next level _get(error, 'response.data.validationFailures.0.field'), which I don't understand. You could do something like this:
const { translate } = useLocales();
const failure = _get(error, 'response.data.validationFailures.0');
if (!failure) {
// do something if you don't have an error message
}
const text = translate(failure.field, failure.code);
Typescript Playground
I'm currently trying to switch from TS 2.6 to 3.4 and I'm running into weird problems. This previously worked, but now it's showing me a compiler error:
type MyNumberType = 'never' | 'gonna';
type MyStringType = 'give' | 'you';
type MyBooleanType = 'up'
interface Bar {
foo(key: MyNumberType): number;
bar(key: MyStringType): string;
baz(key: MyBooleanType): boolean;
}
function test<T extends keyof Bar>(bar: Bar, fn: T) {
let arg: Parameters<Bar[T]>[0];
bar[fn](arg); // error here
}
Error as follows:
Argument of type 'Parameters<Bar[T]>' is not assignable to parameter of type 'never'.
Type 'unknown[]' is not assignable to type 'never'.
Type '[number] | [string] | [boolean]' is not assignable to type 'never'.
Type '[number]' is not assignable to type 'never'.
The Typescript playground tells me that the expected argument of the function is of type 'never':
I don't expect an error here at all. There is only one function argument, and the type of the argument gets inferred via Parameters. Why is does the function expect never?
The problem is that the compiler does not understand that arg and bar[fn] are correlated. It treats both of them as uncorrelated union types, and thus expects that every combination of union constituents is possible when most combinations are not.
In TypeScript 3.2 you'd've just gotten an error message saying that bar[fn] doesn't have a call signature, since it is a union of functions with different parameters. I doubt that any version of that code worked in TS2.6; certainly the code with Parameters<> wasn't in there since conditional types weren't introduced until TS2.8. I tried to recreate your code in a TS2.6-compatible way like
interface B {
foo: MyNumberType,
bar: MyStringType,
baz:MyBooleanType
}
function test<T extends keyof Bar>(bar: Bar, fn: T) {
let arg: B[T]=null!
bar[fn](arg); // error here
}
and tested in TS2.7 but it still gives an error. So I'm going to assume that this code never really worked.
As for the never issue: TypeScript 3.3 introduced support for calling unions of functions by requiring that the parameters be the intersection of the parameters from the union of functions. That is an improvement in some cases, but in your case it wants the parameter to be the intersection of a bunch of distinct string literals, which gets collapsed to never. That's basically the same error as before ("you can't call this") represented in a more confusing way.
The most straightforward way for you to deal with this is to use a type assertion, since you are smarter than the compiler in this case:
function test<T extends keyof Bar>(bar: Bar, fn: T) {
let arg: Parameters<Bar[T]>[0] = null!; // give it some value
// assert that bar[fn] takes a union of args and returns a union of returns
(bar[fn] as (x: typeof arg) => ReturnType<Bar[T]>)(arg); // okay
}
A type assertion is not safe, you this does let you lie to the compiler:
function evilTest<T extends keyof Bar>(bar: Bar, fn: T) {
// assertion below is lying to the compiler
(bar[fn] as (x: Parameters<Bar[T]>[0]) => ReturnType<Bar[T]>)("up"); // no error!
}
So you should be careful. There is a way to do a completely type safe version of this, forcing the compiler to do code flow analysis on every possibility:
function manualTest<T extends keyof Bar>(bar: Bar, fn: T): ReturnType<Bar[T]>;
// unions can be narrowed, generics cannot
// see https://github.com/Microsoft/TypeScript/issues/13995
// and https://github.com/microsoft/TypeScript/issues/24085
function manualTest(bar: Bar, fn: keyof Bar) {
switch (fn) {
case 'foo': {
let arg: Parameters<Bar[typeof fn]>[0] = null!
return bar[fn](arg);
}
case 'bar': {
let arg: Parameters<Bar[typeof fn]>[0] = null!
return bar[fn](arg);
}
case 'baz': {
let arg: Parameters<Bar[typeof fn]>[0] = null!
return bar[fn](arg);
}
default:
return assertUnreachable(fn);
}
}
But that is so brittle (requires code changes if you add methods to Bar) and repetitive (identical clauses over and over) that I usually prefer the type assertion above.
Okay, hope that helps; good luck!
The way barFn is declared is equivalent to:
type Foo = (key: number) => number;
type Bar = (key: string) => string;
type Baz = (key: boolean) => boolean;
function test1(barFn: Foo | Bar | Baz) {
barFn("a"); // error here
}
barFn's parameter will be an intersection of types rather than union. The type never is a bottom type and it happens because there is no intersection between number, string and boolean.
Ideally you want to declare barFn as an intersection of the three function types. e.g:
function test2(barFn: Foo & Bar & Baz) {
barFn("a"); // no error here
}
I have a standard componentDidCatch in my component, which is typed like below and calls an external Logging library from within:
componentDidCatch(error: Error, info: React.ErrorInfo) {
Logger.logErrorMessage(error.message, info);
}
However the 3rd party logging library logErrorMessage function is typed something like:
type ArbitraryValue = undefined | string | number | boolean | ArbitraryObject;
interface ArbitraryObject {
[p: string]: ArbitraryValue;
}
...
logErrorMessage(message: string, info: ArbitraryObject): void;
The TS error message displays the following
.../ErrorBoundary.tsx:38:82 - error TS2345: Argument of type 'ErrorInfo' is not assignable to parameter of type 'ArbitraryObject'.
Index signature is missing in type 'ErrorInfo'.
38 Logger.logErrorMessage(error, info);
~~~~
What is the best way to get my info parameter of componentDidCatch to adhere to the ArbitraryObject interface of logErrorMessage? Optimally without having to change the Logger library source.
Im trying to validate a JSON object in Go. I'm trying to see if the 'tags' attribute is an array.( Later on I'll also want to know if another attribute is an object too).
I have reached to this. If I print reflect.TypeOf(gjson.Get(api_spec, "tags").Value() I get :
string // When the field is a string
[]interface {} // When the field is an array
map[string]interface {} // When the field is an object
But when trying to test this on the below code :
if ( gjson.Get(api_spec, "tags").Exists() ) {
if ( reflect.TypeOf(gjson.Get(api_spec, "tags").Value()) != "[]interface {}" ) {
// some code here ...
}
}
I get the below error code :
invalid operation: reflect.TypeOf(gjson.Get(api_spec, "tags").Value()) != "[]interface {}" (mismatched types reflect.Type and string)
Thanks in advance!
Use a type assertion to determine if a value is a []interface{}:
v := gjson.Get(api_spec, "tags").Value()
_, ok := v.([]interface{}) // ok is true if v is type []interface{}
Here's the code in the question modified to use a type assertion:
if gjson.Get(api_spec, "tags").Exists() {
if _, ok := gjson.Get(api_spec, "tags").Value().([]interface{}); !ok {
// some code here ...
}
}
There's no need to use reflection. If you do want to use reflection for some reason (and I don't see a reason in the question), then compare reflect.Type values:
// Get type using a dummy value. This can be done once by declaring
// the variable as a package-level variable.
var sliceOfInterface = reflect.TypeOf([]interface{}{})
ok = reflect.TypeOf(v) == sliceOfInterface // ok is true if v is type []interface{}
run the code on the playground
When you print a type to console, it's converted to a string; however, as you can see from the documentation for TypeOf, it does not return a string, it returns a reflect.Type. You can use Kind() to test programmatically what it is:
if reflect.TypeOf(gjson.Get(api_spec, "tags").Value()).Kind() != reflect.Slice {
Other Kinds you might be interested in are reflect.String and reflect.Map.
reflect.TypeOf returns a Type object. See docs at https://golang.org/pkg/reflect/#TypeOf
Your code should read:
if reflect.TypeOf(gjson.Get(api_spec, "tags").Value()).Name() != "[]interface {}" {
// some code here ...
}
I have such map:
Map := make(map[string]interface{})
This map is supposed to contain mapping from string to array of objects. Arrays can be of different types, like []Users or []Hosts. I populated this array:
TopologyMap["Users"] = Users_Array
TopologyMap["Hosts"] = Hosts_Array
but when I try to get an elements from it:
Map["Users"][0]
it gives an error:
(type interface {} does not support indexing)
How can I overcome it?
You have to explicitly convert your interface{} to a slice of the expected type to achieve it. Something like this:
package main
import "fmt"
type Host struct {
Name string
}
func main() {
Map := make(map[string]interface{})
Map["hosts"] = []Host{Host{"test.com"}, Host{"test2.com"}}
hm := Map["hosts"].([]Host)
fmt.Println(hm[0])
}
Playground link
First thing to be noted is the interface{} can hold any data type including function and struct or []struct. Since the error gives you :
(type interface {} does not support indexing)
It means that it holds no slice or no array values. Because you directly call the index in this case is 0 to an interface{} and you assume that the Map["Users"] is an array. But it is not. This is one of very good thing about Go it is statically type which mean all the data type is check at compiled time.
if you want to be avoid the parsing error like this:
panic: interface conversion: interface {} is []main.User, not
[]main.Host
To avoid that error while your parsing it to another type like Map["user"].([]User) just in case that another data type pass to the interface{} consider the code snippet below :
u, ok := myMap["user"].([]User)
if ok {
log.Printf("value = %+v\n", u)
}
Above code is simple and you can use it to check if the interface match to the type you are parsing.
And if you want to be more general passing the value to your interface{} at runtime you can check it first using reflect.TypeOf() please consider this code :
switch reflect.TypeOf(myMap["user"]).String() {
case "[]main.User":
log.Println("map = ", "slice of user")
logger.Debug("map = ", myMap["user"].([]User)[0])
case "[]main.Host":
log.Println("map = ", "slice of host")
logger.Debug("map = ", myMap["user"].([]Host)[0])
}
after you know what's the value of the interface{} you can confidently parse it the your specific data type in this case slice of user []User. Not that the main there is a package name you can change it to yours.
This is how I solved it for unstructured data. You have to parse to index string until you reach the end. Then you can print key value pairs.
yamlStr := `
input:
bind: 0.0.0.0:12002
interface: eth0
reaggregate: {}
versions: {}
output:
destination: owl-opsw-sw-dev-4.opsw:32001
interface: eth0
`
var obj map[string]interface{}
if err := yaml.Unmarshal([]byte(yamlStr), &obj); err != nil {
// Act on error
}
// Set nested object to map[string]
inputkv := streamObj["input"].(map[string]interface{})
for key, value := range inputkv {
// Each value is an interface{} type, that is type asserted as a string
fmt.Println(key, value.(string))
}
Result
bind 0.0.0.0:12002
interface eth0