Skip to content
This repository was archived by the owner on May 8, 2021. It is now read-only.

Commit e3f5d71

Browse files
authored
Adding simple get (#2)
* - Simple ts-get added for better typings in corner cases * - Deploy and build scripts are fixed. Few other improvements
1 parent 217c5f6 commit e3f5d71

14 files changed

Lines changed: 638 additions & 1329 deletions

File tree

.babelrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"comments": false,
3+
"presets": [
4+
[
5+
"@babel/preset-env",
6+
{
7+
"targets": {
8+
"node": "10"
9+
}
10+
}
11+
],
12+
"@babel/preset-typescript"
13+
]
14+
}

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ npm-debug.log.*
5050
/selenium-debug.log
5151
/grocerycop-website-3.1-ui.iml
5252
/.vscode/
53-
/lib/
53+
/**/lib
5454
/.env.local
5555
/styleguide/
5656
/storybook-static/

README.md

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Alternative to `lodash.get` that makes it typed and cool as if optional chaining
77

88
Means you're not only safely navigate object, but you're also getting 100% autocomplete and type-safeness 🎉
99

10+
## Important
11+
12+
There is also `ts-get-simple` that is exactly the same, but will require you to use `!` to be able to chain, it improves some corner cases
13+
(e.g. when you chain to some object, and non primitive, it will persist original nested type)
14+
Take a look at the examples below.
15+
1016
## Usage and examples
1117

1218
```typescript
@@ -19,26 +25,87 @@ import get from 'ts-get'
1925
}
2026
} | undefined | null
2127

22-
const input: SomeType = {}
23-
const input2: SomeType = {
28+
const emptyObject: SomeType = {}
29+
const withOneOptionalField: SomeType = {
2430
optionalField: "value",
2531
}
2632

27-
get(input, it => it.optionalField, "default") // -> "default"
28-
get(input2, it => it.optionalField, "default") // -> "value"
29-
get(input2, it => it.nested.dangerousAccess, "default") // -> "default"
30-
get(input2, it => it.unknownField, "default") // -> Type error, `unknownField` doesn't exist on type
31-
get(input2, it => it.optionalField, 5) // -> Type error, third argument is not assignable to type `string`
33+
get(emptyObject, it => it.optionalField, "default") // -> "default"
34+
get(withOneOptionalField, it => it.optionalField, "default") // -> "value"
35+
get(withOneOptionalField, it => it.nested.dangerousAccess, "default") // -> "default"
36+
get(withOneOptionalField, it => it.unknownField, "default") // -> Type error, `unknownField` doesn't exist on type
37+
get(withOneOptionalField, it => it.optionalField, 5) // -> Type error, third argument is not assignable to type `string`
3238
```
3339

3440
## Difference with `lodash.get` behavior
3541

3642
- If your path gets `null` at the end, it will bail out to `defaultValue` or `undefined`.
3743
If you would like to get `null` returned anyway, just pass it as a `defaultValue`
44+
45+
## Known issues:
3846
- If your type field is of type `null` and only `null` or `undefined` your field will be of type `{}[]`.
3947
I have no idea how to fix it 🤷‍♂️ PR Welcome 😇🙏
4048
```typescript
4149
type A = {
4250
field: null | undefined// -> {}[] inside of the callback and as return type too
4351
}
44-
```
52+
53+
```
54+
- If you return not a primitive but an object, all its nested fields will be `Required` e.g. all `undefined` and `null` will be removed.
55+
```typescript
56+
import get from 'ts-get'
57+
type A = {
58+
field?: {
59+
optional?: string | null
60+
}
61+
}
62+
const input: A = {}
63+
const res = get(input, it => it.field)
64+
res // <== Will be inferred as { optional: string }, without null and ? (undefined) which is wrong, but seems to be impossible to infer.
65+
66+
```
67+
68+
69+
# ts-get-simple
70+
[![npm version](https://badge.fury.io/js/ts-get.svg)](https://badge.fury.io/js/ts-get-simple)
71+
72+
73+
74+
## Usage and examples `ts-get-simple`
75+
76+
```typescript
77+
import get from 'ts-get-simple'
78+
79+
type SomeType = {
80+
optionalField?: string
81+
nested?: {
82+
dangerousAccess?: string
83+
}
84+
} | undefined | null
85+
86+
const emptyObject: SomeType = {}
87+
const withOneOptionalField: SomeType = {
88+
optionalField: "value",
89+
}
90+
91+
get(emptyObject, it => it!.optionalField, "default") // -> "default"
92+
get(withOneOptionalField, it => it!.optionalField, "default") // -> "value"
93+
get(withOneOptionalField, it => it!.nested!.dangerousAccess, "default") // -> "default"
94+
get(withOneOptionalField, it => it!.unknownField, "default") // -> Type error, `unknownField` doesn't exist on type
95+
get(withOneOptionalField, it => it!.optionalField, 5) // -> Type error, third argument is not assignable to type `string | undefined`
96+
```
97+
98+
## Known typing issues of `ts-get-simple`
99+
100+
```typescript
101+
import get from 'ts-get-simple'
102+
103+
type SomeType = {
104+
nested?: string | null
105+
}
106+
const input: SomeType = {}
107+
const cannotBeNull = get(input, it => it!.nested) // <== will be inferred as string | null | undefined, while it really cannot be `null` by logic
108+
const canBeNull = get(input, it => it!.nested, null) // <== will be inferred as string, while it really should be string | null
109+
const canBeUndefined = get(input, it => it!.nested, undefined) // <== will be inferred as string, while it really should be string | undefined
110+
```
111+
Last one is stupid, but just for your information.

package.json

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ts-get",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "Alternative to lodash.get that makes it typed and cool as if optional chaining proposal is there",
55
"main": "lib/index.node.js",
66
"module": "lib/index.js",
@@ -9,49 +9,40 @@
99
"repository": "https://github.com/RIP21/ts-get.git",
1010
"author": "Andrii Los <puha212@gmail.com>",
1111
"license": "MIT",
12-
"private": false,
12+
"private": true,
1313
"scripts": {
1414
"lint": "prettier --write ./**/*.ts",
15-
"prebuild": "rimraf lib",
16-
"build": "tsc && babel src/index.ts --out-file lib/index.node.js",
15+
"clean": "rimraf src/ts-get/lib && rimraf src/ts-get-simple/lib && mkdir src/ts-get/lib && mkdir src/ts-get-simple/lib",
16+
"prebuild": "yarn clean",
17+
"build:smart": "tsc -p src/ts-get/tsconfig.json && babel src/ts-get/index.ts --out-file src/ts-get/lib/index.node.js --config-file ./.babelrc",
18+
"build:simple": "tsc -p src/ts-get-simple/tsconfig.json && babel src/ts-get-simple/index.ts --out-file src/ts-get-simple/lib/index.node.js --config-file ./.babelrc",
19+
"build": "run-p build:*",
1720
"test": "jest src --coverage",
1821
"test:codecov": "jest src --coverage && codecov",
1922
"prepublish": "yarn build",
20-
"release": "np"
23+
"release:smart": "cd src/ts-get && np --any-branch && cd ../..",
24+
"release:simple": "cd src/ts-get-simple && np --any-branch && cd ../..",
25+
"release": "run-s release:*"
2126
},
2227
"devDependencies": {
2328
"@babel/cli": "^7.2.3",
24-
"@babel/core": "^7.3.4",
29+
"@babel/core": "^7.4.0",
2530
"@babel/plugin-syntax-typescript": "^7.3.3",
26-
"@babel/preset-env": "^7.3.4",
31+
"@babel/preset-env": "^7.4.2",
2732
"@babel/preset-typescript": "^7.3.3",
28-
"@babel/runtime": "^7.3.4",
33+
"@babel/runtime": "^7.4.2",
2934
"@types/jest": "^24.0.11",
3035
"@types/lodash": "^4.14.123",
3136
"codecov": "^3.2.0",
3237
"jest": "^24.5.0",
3338
"lodash": "^4.17.11",
34-
"np": "^4.0.2",
39+
"npm-run-all": "^4.1.5",
3540
"prettier": "^1.16.4",
36-
"rimraf": "^2.6.3",
37-
"ts-jest": "^24.0.0",
38-
"typescript": "^3.3.3333"
41+
"rimraf": "2.6.3",
42+
"ts-jest": "24.0.1",
43+
"typescript": "3.4.1"
3944
},
4045
"files": [
4146
"lib"
42-
],
43-
"babel": {
44-
"comments": false,
45-
"presets": [
46-
[
47-
"@babel/preset-env",
48-
{
49-
"targets": {
50-
"node": "10"
51-
}
52-
}
53-
],
54-
"@babel/preset-typescript"
55-
]
56-
}
47+
]
5748
}

src/ts-get-simple/index.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { get } from './'
2+
import { get as _get } from 'lodash'
3+
4+
describe('get', function() {
5+
type InputType = {
6+
a: string
7+
b?: {
8+
nested?: string | null
9+
}
10+
}
11+
const inputAllPresent: InputType = {
12+
a: 'Value',
13+
b: {
14+
nested: 'Value',
15+
},
16+
}
17+
const inputOptionalsMissing: InputType = {
18+
a: 'Value',
19+
}
20+
const inputOptionalsSafeAccessMissing: InputType = {
21+
a: 'Value',
22+
b: {}, // accessing b.nested will return undefined and will not throw error
23+
}
24+
it('should return value', () => {
25+
const result = get(inputAllPresent, it => it.a)
26+
expect(result).toBe('Value')
27+
})
28+
it('should return value on existing optional field', () => {
29+
const result = get(inputAllPresent, it => it!.b!.nested, 'Default')
30+
expect(result).toBe('Value')
31+
})
32+
it('should return undefined on non existing optional field', () => {
33+
const result = get(inputOptionalsMissing, it => it!.b!.nested) // Here is wrong typings, it's string | null | undefined, where is should be only string | undefined
34+
expect(result).toBeUndefined()
35+
})
36+
it('should return defaultValue on non existing optional field', () => {
37+
const result = get(inputOptionalsMissing, it => it!.b!.nested, 'Default')
38+
expect(result).toBe('Default')
39+
})
40+
it('should return defaultValue on non existing optional field', () => {
41+
const result = get(inputOptionalsMissing, it => it!.b!.nested, 'Default')
42+
expect(result).toBe('Default')
43+
})
44+
45+
it('should replica lodash interface in cases below', () => {
46+
const result1 = get(inputAllPresent, it => it.a)
47+
const result1_ = _get(inputAllPresent, 'a')
48+
49+
const result2 = get(inputAllPresent, it => it!.b!.nested, 'Default')
50+
const result2_ = _get(inputAllPresent, 'b.nested', 'Default')
51+
52+
const result3 = get(inputOptionalsMissing, it => it!.b!.nested) // Here is wrong typings, it's string | null | undefined, where is should be only string | undefined
53+
const result3_ = _get(inputOptionalsMissing, 'b.nested')
54+
55+
const result4 = get(inputOptionalsSafeAccessMissing, it => it!.b!.nested, 'Default')
56+
const result4_ = _get(inputOptionalsSafeAccessMissing, 'b.nested', 'Default')
57+
58+
expect(result1).toBe(result1_)
59+
expect(result2).toBe(result2_)
60+
expect(result3).toBe(result3_)
61+
expect(result4).toBe(result4_)
62+
})
63+
it('should return undefined and default value in case of null (different from _.get)', function() {
64+
const inputWithNull: InputType = {
65+
a: 'Value',
66+
b: {
67+
nested: null,
68+
},
69+
}
70+
const defaultedNull = get( // Here is wrong typings, it's string | null, but it says string
71+
inputOptionalsMissing,
72+
it => it!.b!.nested,
73+
null,
74+
)
75+
expect(defaultedNull).toBe(null)
76+
77+
const undefinedFromNullValue = get(inputWithNull, it => it!.b!.nested) // Here is wrong typings, it's string | null | undefined, where is should be only string | undefined
78+
expect(undefinedFromNullValue).toBeUndefined()
79+
80+
const defaultedStringFromNullValue = get(
81+
inputWithNull,
82+
it => it!.b!.nested,
83+
'Default',
84+
)
85+
expect(defaultedStringFromNullValue).toBe('Default')
86+
})
87+
})

src/ts-get-simple/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export type AccessorFunction<T, R> = (object: T) => R
2+
3+
export function get<T, R>(object: T, accessorFn: AccessorFunction<T, R>): Exclude<R, null> | undefined
4+
export function get<T, R>(
5+
object: T,
6+
accessorFn: AccessorFunction<T, R>,
7+
defaultValue: R,
8+
): Exclude<R, undefined | null>
9+
10+
export function get<T, R>(
11+
object: T,
12+
accessorFn: AccessorFunction<T, R>,
13+
defaultValue?: R,
14+
): R | undefined {
15+
try {
16+
const result = accessorFn(object)
17+
return result === undefined || result === null ? defaultValue : result
18+
} catch (e) {
19+
return defaultValue
20+
}
21+
}
22+
23+
export default get

src/ts-get-simple/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "simple-ts-get",
3+
"version": "1.0.4",
4+
"description": "Alternative to lodash.get that makes it typed and cool as if optional chaining proposal is there",
5+
"main": "lib/index.node.js",
6+
"module": "lib/index.js",
7+
"jsnext:main": "lib/index.js",
8+
"types": "lib/index.d.ts",
9+
"repository": "https://github.com/RIP21/ts-get.git",
10+
"author": "Andrii Los <puha212@gmail.com>",
11+
"license": "MIT",
12+
"private": false,
13+
"files": [
14+
"lib"
15+
]
16+
}

src/ts-get-simple/tsconfig.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "./",
4+
"lib": ["es6", "dom", "es2016", "es2017"],
5+
"module": "esnext",
6+
"moduleResolution": "node",
7+
"strict": true,
8+
"outDir": "lib",
9+
"declaration": true,
10+
"esModuleInterop": true,
11+
"sourceMap": true
12+
},
13+
"exclude": [
14+
"/**/index.test.ts"
15+
]
16+
}
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('get', function() {
1919
}
2020
const inputOptionalsSafeAccessMissing: InputType = {
2121
a: 'Value',
22-
b: {} // accessing b.nested will return undefined and will not throw error
22+
b: {}, // accessing b.nested will return undefined and will not throw error
2323
}
2424
it('should return value', () => {
2525
const result = get(inputAllPresent, it => it.a)
@@ -55,7 +55,6 @@ describe('get', function() {
5555
const result4 = get(inputOptionalsSafeAccessMissing, it => it.b.nested, 'Default')
5656
const result4_ = _get(inputOptionalsSafeAccessMissing, 'b.nested', 'Default')
5757

58-
5958
expect(result1).toBe(result1_)
6059
expect(result2).toBe(result2_)
6160
expect(result3).toBe(result3_)
@@ -68,13 +67,17 @@ describe('get', function() {
6867
nested: null,
6968
},
7069
}
71-
const defaultedNull = get<InputType, string | null>(inputOptionalsMissing, it => it.b.nested, null)
70+
const defaultedNull = get<InputType, string | null>(
71+
inputOptionalsMissing,
72+
it => it.b.nested,
73+
null,
74+
)
7275
expect(defaultedNull).toBe(null)
7376

7477
const undefinedFromNullValue = get(inputWithNull, it => it.b.nested)
7578
expect(undefinedFromNullValue).toBeUndefined()
7679

77-
const defaultedStringFromNullValue = get(inputWithNull, it => it.b.nested, "Default")
78-
expect(defaultedStringFromNullValue).toBe("Default")
80+
const defaultedStringFromNullValue = get(inputWithNull, it => it.b.nested, 'Default')
81+
expect(defaultedStringFromNullValue).toBe('Default')
7982
})
8083
})

0 commit comments

Comments
 (0)