I am trying to document a factory function in my declaration file.
My goal is to make flow aware of my simple factory.
It's used in koa v2 routes and it's a way to inject some options in my service.
Here is the factory:
ctx.compose = function Compose<T: *>(service: Class<T>, options: ?Object): T {
return new service(_.extend({}, ctx._requestOptions, options));
};
Because I use koa v2 I created a type KoaCtx in a declaration that look like this:
declare type KoaCtx = {
params: { [key: string]: string },
request: {
query: { [key: string]: string },
body: { [key: string]: string | boolean | Array<any> | Number | Date | Object },
},
body: any,
req: any,
res: any,
state: any,
...
compose: compose: function <T: *>(service: Class<T>, options: ?Object): T
}
I tried different syntaxes but I keep getting errors.
compose: function <T: *>(service: Class<T>, options: ?Object): T
^ Unexpected token :
If I put the first snippet of code inside my koa route it's working fine!
I tried to add the file with the ctx.compose definition in [include] tag in flow config but it's not working.
Update
Tried with this declaration:
declare function Compose<T: *>(service: Class<T>, options: ?Object): T;
declare type KoaCtx = {
...
compose: Compose<Class<*>>
};
But unfortunately it's still not working.
A function type looks like
(argName1: Type1, argName2: Type2, ...restName: Array<RestType>) => ReturnType
a function type with generics looks like
<T>(argName1: Type1, argName2: Type2, ...restName: Array<RestType>) => ReturnType
So you probably should write
declare type KoaCtx = {
...
compose: <T: *>(service: Class<T>, options: ?Object) => T
}
That said, you're using the existential type * as an upper bound for T. I'm not sure if that makes much sense here. So I'd just recommend writing
declare type KoaCtx = {
...
compose: <T>(service: Class<T>, options: ?Object) => T
}
As for your update, you can only explicitly instantiate type parameters for type aliases, interfaces, and classes. So you could write
declare type Compose = <T>(service: Class<T>, options: ?Object) => T;
declare type KoaCtx = {
...
compose: Compose<MyClass>
};
but you can't do that if Compose is a generic function.
Related
I am just curious how can I encode a dictionary with String key and Encodable value into JSON.
For example:
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
The keys in this dict are all of type String, but the type of the values vary.
However, all of these types are allowed in JSON.
I am wondering if there is a way to use JSONEncoder in Swift 4 to encode this dict into JSON Data.
I do understand there are other ways without using JSONEncoder to achieve this, but I am just wondering if JSONEncoder is capable of managing this.
The Dictionary do have a func encode(to encoder: Encoder) throws in an extension, but that only applies for constraint Key: Encodable, Key: Hashable, Value: Encodable, whereas for our dict, it needs constraint Key: Encodable, Key: Hashable, Value == Encodable.
Having a struct for this will be sufficient to use JSONEncoder,
struct Test: Encodable {
let int = 1
let double = 3.14
let bool = false
let string = "test"
}
However, I am interested to know if the it can be done without specifying the concrete type but just the Encodable protocol.
Just figured out a way to achieve this with a wrapper:
struct EncodableWrapper: Encodable {
let wrapped: Encodable
func encode(to encoder: Encoder) throws {
try self.wrapped.encode(to: encoder)
}
}
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
let wrappedDict = dict.mapValues(EncodableWrapper.init(wrapped:))
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(wrappedDict)
let json = String(decoding: jsonData, as: UTF8.self)
print(json)
And here is the result:
{
"Double" : 3.1400000000000001,
"String" : "test",
"Bool" : false,
"Int" : 1
}
I am still not happy with it. If there are any other approaches, I am more than happy to see it.
Thanks!
Edit 1 Moving the wrapper into an extension of JSONEncoder:
extension JSONEncoder {
private struct EncodableWrapper: Encodable {
let wrapped: Encodable
func encode(to encoder: Encoder) throws {
try self.wrapped.encode(to: encoder)
}
}
func encode<Key: Encodable>(_ dictionary: [Key: Encodable]) throws -> Data {
let wrappedDict = dictionary.mapValues(EncodableWrapper.init(wrapped:))
return try self.encode(wrappedDict)
}
}
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(dict)
let json = String(decoding: jsonData, as: UTF8.self)
print(json)
Result:
{
"Int" : 1,
"Double" : 3.1400000000000001,
"Bool" : false,
"String" : "test"
}
Edit 2: Take customized strategies into account as per #Hamish 's comments
private extension Encodable {
func encode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
extension JSONEncoder {
private struct EncodableWrapper: Encodable {
let wrapped: Encodable
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try self.wrapped.encode(to: &container)
}
}
func encode<Key: Encodable>(_ dictionary: [Key: Encodable]) throws -> Data {
let wrappedDict = dictionary.mapValues(EncodableWrapper.init(wrapped:))
return try self.encode(wrappedDict)
}
}
You would need a wrapper since with Encodable protocol to know which item is which to be able to encode it easier.
I suggest Use an enum named JSONValue which has 5 to 6 cases for all Int, String, Double, Array, Dictionary cases. then you can write JSONs in a type-safe way.
This link will help too.
This is how I use it:
indirect enum JSONValue {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
case object([String: JSONValue])
case array([JSONValue])
case encoded(Encodable)
}
And then make JSONValue: Encodable and write encoding code for each case.
I'm struggling with flowtype declaration for a generic function with different pairs of parameters.
My goal is to have a function which return an object of certain union type depending on input parameters.
I'm having a big load of messages that i want to type (for this example i'm using only two)
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
type MessageFactory<T> = (type: $PropertyType<T, 'type'>, payload: $PropertyType<T, 'payload'>) => T;
export const factory: MessageFactory<Message1> = (type, payload) => {
return {
event: 'UI',
type,
payload
}
}
factory('receive', true);
// factory('send', { foo: true, bar: "bar" });
when i change
MessageFactory<Message1>
to
MessageFactory<Message1 | Message2>
it will throw an error
Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `payload` [3] or to `type` [4]
You can ty it here
any idea how to declare this function?
or is it stupid idea and i'm going to the wrong direction?
any better solutions?
Create a GenericMessage with type parameters for your desired properties (type and payload), then have your factory return a GenericMessage:
(Try)
type GenericMessage<TYPE: string, PAYLOAD> = {
event: 'UI',
type: TYPE,
payload: PAYLOAD
}
const factory = <T: string, P>(type: T, payload: P): GenericMessage<T, P> => {
return {
event: 'UI',
type,
payload
}
}
const test1 = factory('receive', true);
const test2 = factory('send', { foo: true, bar: "bar" });
// Let's check the new type against Message1 and Message2:
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
// Type assertions
(test1: Message1);
(test2: Message2);
(test1: Message2); // Error!
If you want, you can create a MessageFactory type that returns a GenericMessage<T, P>. You can also create an EVENT type parameter if you need to control the event property on the object.
(You don't need to call it GenericMessage, I just called it that to make a distinction between your existing types and this new one)
With this example:
const myObj = {
test: true,
};
type MyType = typeof myObj;
const getValue = (): MyType => {
return myObj;
};
// how to do this??
type TheReturnType = getValue;
const nextObj: TheReturnType = {
test: false,
};
I'd like to extract the type that the function will return, so I can reuse that type. I can think of no way to get it. The above doesn't work. typeof getValue will return the function.
Flow has a $Call utility type, which can get a function's return type:
type TheReturnType = $Call<typeof getValue>
However, if your function takes arguments, you need to provide types for those as well:
type TimeoutType = $Call<typeof setTimeout, () => void, number>
If that seems inconvenient, you can write a ReturnType helper that can skip the need for arguments:
type ReturnType<F> =
$PropertyType<$ObjMap<{ x: F }, <R>(f: (...any) => R) => R>, 'x'>
Let's use this:
type TheReturnType = ReturnType<typeof setTimeout>
This ReturnType helper basically matches the ReturnType helper present in TypeScript.
I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
I am trying to create a higher order component, Hoc, that gives its children some extra props through React.cloneElement. I have not been able to get flowtype to know that the extra props were in fact passed down.
Below is my failed attempt, which throws the error foo type cannot be found on object literal. I would like to know what I can do to fix this.
type Props = {
foo: string,
bar: string,
};
type DefaultProps = {
foo: string,
};
declare class React2$Element<Config, DP> extends React$Element{
type: _ReactClass<DP, *, Config, *>;
}
declare function Hoc<Config, DP: DefaultProps, R: React$Element<Config>>(props: {children: R}) : React2$Element<Config, DP>
function TestComponent({foo, bar}: Props){
return <div>{bar}</div>;
}
function Hoc(props){
return React.cloneElement(props.children, {foo: 'form2wr'});
}
function Test(){
return <Hoc children={<TestComponent bar='yo' />}></Hoc>;
}
I don't have an answer to this question, but I do have a workaround.
type Props = {
foo: string,
bar: string,
};
type DefaultProps = {
foo: string,
};
type WithHOCProps<X> = $Diff<X, DefaultProps>
declare function TestComponent(props: WithHOCProps<Props>) : React$Element;
function TestComponent({foo, bar}: Props){
return <div>{foo + bar}</div>;
}
function Test(){
return <TestComponent bar='yo' />;
}
Tadahhh, no errors.