Skip to content

Commit

Permalink
feat: #30 add function "pick", "omit" (#31)
Browse files Browse the repository at this point in the history
* feat: add omit and pick function

* chore: update docs

* Create chatty-dryers-tickle.md
  • Loading branch information
mew-ton authored Oct 5, 2023
1 parent a7bd2c0 commit e5916c1
Show file tree
Hide file tree
Showing 16 changed files with 788 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-dryers-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"json-origami": minor
---

feat: #30 add function "pick", "omit"
79 changes: 76 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ Whether you're looking to flatten a nested structure or revert a flat map into i
## Key Features

- **Lightweight & Standalone**: Operates without any external dependencies, ensuring quick installations and reduced bundle sizes.
- **fold**: Flatten a multi-layered JSON object into a single-tiered representation.
- **unfold**: Transform a single-layer JSON map back to its original nested structure.
- **twist**: Reshape and reorganize the keys of your JSON structure, mirroring the intricate adjustments made in origami.
- **Simple & Intuitive**: Designed with a simple API that is easy to learn and use.

## Installation

Expand All @@ -25,6 +23,8 @@ npm install json-origami

### fold

Flatten a multi-layered JSON object into a single-tiered representation.

```javascript
import { fold } from 'json-origami'

Expand All @@ -42,6 +42,8 @@ console.log(result)

### unfold

Transform a single-layer JSON map back to its original nested structure.

```javascript
import { unfold } from 'json-origami'

Expand All @@ -59,6 +61,8 @@ console.log(result)

### twist

Reshape and reorganize the keys of your JSON structure, mirroring the intricate adjustments made in origami.

```javascript
import { twist } from 'json-origami'

Expand All @@ -81,6 +85,46 @@ console.log(result)
/// { foo: 1, bar: 2, baz: [3, { e: 4 }] }
```

### pick

Select only the specified keys from a JSON object.

```javascript
import { pick } from 'json-origami'

const obj = {
a: 1,
b: {
c: 2,
d: [3, { e: 4 }]
}
}

const result = pick(obj, ['a', 'b.c'])
console.log(result)
/// { a: 1, b: { c: 2 } }
```

### omit

Remove the specified keys from a JSON object.

```javascript
import { omit } from 'json-origami'

const obj = {
a: 1,
b: {
c: 2,
d: [3, { e: 4 }]
}
}

const result = omit(obj, ['b.c', 'b.d[1]'])
console.log(result)
/// { a: 1, b: { d: [3] } }
```

## Options

### arrayIndex
Expand All @@ -106,6 +150,35 @@ conole.log(result)
/// { a: 1, 'b.c': 2, 'b.d.0': 3, 'b.d.1.e': 4 }
```

### pruneArray

Options for 'unfold', 'twist', 'pick', 'omit'

**Type**: `boolean`
**Default**: `true`

Specifies whether to remove the specified array elements or not.

```javascript
import { twist } from 'json-origami'

const obj = {
a: 1,
b: {
c: 2,
d: [3, 4, 5, 6]
}
}

const result1 = twist(obj, { 'b.d[1]': 'D' }, { pruneArray: true })
console.log(result1)
/// { a: 1, b: { c: 2, d: [3, 5, 6] }, D: 4 }

const result2 = twist(obj, { 'b.d[1]': 'D' }, { pruneArray: false })
console.log(result2)
/// { a: 1, b: { c: 2, d: [3, undefined, 5, 6] }, D: 4 }
```

### keyPrefix

Options for 'fold'
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"access": "public"
},
"scripts": {
"dev": "vitest",
"dev": "vitest --ui",
"build": "tsup",
"test": "run-p test:*",
"test:spec": "vitest --run --silent",
Expand All @@ -50,6 +50,7 @@
"@changesets/cli": "^2.26.2",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"@vitest/ui": "^0.34.6",
"eslint": "^8.50.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.1",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export type { Folded, Unfolded } from './type'
export { fold } from './fold'
export { unfold } from './unfold'
export { twist } from './twist'
export { omit } from './omit'
export { pick } from './pick'
48 changes: 48 additions & 0 deletions src/omit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { fold } from './fold'
import { unfold } from './unfold'
import { includesKey } from './utils'
import type { Dictionary, DeepKeyOf, OmitOption, Omit, Folded } from './type'

/**
* Returns an object with the specified keys removed from the object.
*
* @example
* ```ts
* const obj = {
* a: 1,
* b: {
* c: 2,
* d: [3, 4]
* }
* }
*
* const omitted = omit(obj, ['a', 'b.c'])
* // omitted is
* // {
* // b: {
* // d: [3, 4]
* // }
* // }
* ```
*
* @param obj
* @param keys
* @param opt
*/
export function omit<D extends Dictionary, K extends DeepKeyOf<D>>(
obj: D,
keys: K[],
opt?: OmitOption
): Omit<D, K> {
const folded = fold(obj)

const targetKeys = new Set(
Object.keys(folded).filter((k) => !keys.some((key) => includesKey(key, k, opt)))
)

const fixedKeyMap = Object.fromEntries(
Object.entries(folded).filter(([k]) => targetKeys.has(k))
) as Folded<Dictionary>

return unfold(fixedKeyMap, opt) as Dictionary
}
49 changes: 49 additions & 0 deletions src/pick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { fold } from './fold'
import { unfold } from './unfold'
import { includesKey } from './utils'
import type { Dictionary, DeepKeyOf, PickOption, Omit, Folded } from './type'

/**
* Returns an object with the specified keys picked from the object.
*
* @example
* ```ts
* const obj = {
* a: 1,
* b: {
* c: 2,
* d: [3, 4]
* }
* }
*
* const omitted = pick(obj, ['a', 'b.c'])
* // omitted is
* // {
* // a: 1,
* // b: {
* // c: 2
* // }
* // }
* ```
*
* @param obj
* @param keys
* @param opt
*/
export function pick<D extends Dictionary, K extends DeepKeyOf<D>>(
obj: D,
keys: K[],
opt?: PickOption
): Omit<D, K> {
const folded = fold(obj)

const targetKeys = new Set(
Object.keys(folded).filter((k) => keys.some((key) => includesKey(key, k, opt)))
)

const fixedKeyMap = Object.fromEntries(
Object.entries(folded).filter(([k]) => targetKeys.has(k))
) as Folded<Dictionary>

return unfold(fixedKeyMap, opt) as Dictionary
}
5 changes: 3 additions & 2 deletions src/twist.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { fold, unfold } from '.'
import { fold } from './fold'
import { unfold } from './unfold'
import type { Dictionary, MoveMap, Twist, TwistOption } from './type'

/**
*
* @param obj
* @param moveMap
*/
export function twist<D extends Dictionary, M extends MoveMap>(
export function twist<D extends Dictionary, M extends MoveMap<D>>(
obj: D,
moveMap: M,
option?: TwistOption
Expand Down
79 changes: 71 additions & 8 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export type Dictionary =
| MaybeReadonly<Record<string, unknown>>
| MaybeReadonly<Array<Primitive | Record<string, unknown>>>

/**
* TODO: 深い階層のキーに対応する
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type DeepKeyOf<_D extends Dictionary> = string

/**
*
*/
Expand All @@ -21,7 +27,7 @@ export type Folded<_D extends Dictionary> = Record<string, Primitive>
/**
*
*/
export type MoveMap = Record<string, string>
export type MoveMap<D extends Dictionary> = Record<DeepKeyOf<D>, string>

/**
*
Expand All @@ -33,15 +39,31 @@ export type Unfolded<KV extends Folded<any>> = KV extends Folded<infer D> ? D :
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type Twist<_D extends Dictionary, _M extends MoveMap> = object
export type Twist<D extends Dictionary, _M extends MoveMap<D>> = Dictionary

/**
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type Omit<D extends Dictionary, _K extends DeepKeyOf<D>> = Dictionary

/**
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type Pick<D extends Dictionary, _K extends DeepKeyOf<D>> = Dictionary

/**
*
*/
export type ArrayIndex = 'dot' | 'bracket'

interface CommonOption {
export interface CommonOption {
/**
* Specify the array index style.
* - `dot`: `a.0.b.1.c`
* - `bracket`: `a[0].b[1].c`
*
* @default 'bracet'
*/
arrayIndex?: 'dot' | 'bracket'
Expand All @@ -52,7 +74,8 @@ interface CommonOption {
*/
export interface FoldOption extends CommonOption {
/**
*
* Specify the prefix of the key of the result of fold.
* @default ''
*/
keyPrefix?: string
}
Expand All @@ -66,13 +89,53 @@ export type FixedFoldOption = Readonly<FoldOption & typeof defaultCommonOption>
/**
*
*/
export interface UnfoldOption extends CommonOption {}
export interface UnfoldOption extends CommonOption {
/**
* If true, the array will be pruned if it is empty.
*
* @example
* ```ts
* const kv = {
* 'a[1]': 1,
* 'a[3]': 2,
* 'a[5]': 3
* }
*
* const pruned = unfold(kv, { pruneArray: true })
* // pruned is { a: [1, 2, 3] }
*
* const notPruned = unfold(kv, { pruneArray: false })
* // notPruned is { a: [undefined, 1, undefined, 2, undefined, 3] }
* ```
* @default true
*/
pruneArray?: boolean
}

export const defaultUnfoldOption = {
...defaultCommonOption,
pruneArray: true
} satisfies UnfoldOption

export type FixedUnfoldOption = Readonly<UnfoldOption & typeof defaultUnfoldOption>

/**
*
*/
export interface TwistOption extends UnfoldOption {}

export type FixedTwistOption = Readonly<TwistOption & typeof defaultUnfoldOption>

/**
*
*/
export interface OmitOption extends UnfoldOption {}

export type FixedUnfoldOption = Readonly<UnfoldOption & typeof defaultCommonOption>
export type FixedOmitOption = Readonly<OmitOption & typeof defaultUnfoldOption>

/**
*
*/
export interface TwistOption extends CommonOption {}
export interface PickOption extends UnfoldOption {}

export type FixedTwistOption = Readonly<TwistOption & typeof defaultCommonOption>
export type FixedPickOption = Readonly<PickOption & typeof defaultUnfoldOption>
Loading

0 comments on commit e5916c1

Please sign in to comment.