Skip to content

Commit 0b601db

Browse files
committed
feat: add support for non-standard JSON shapes via mapProp
1 parent f873219 commit 0b601db

5 files changed

Lines changed: 157 additions & 12 deletions

File tree

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,48 @@ const Example = () => {
7575
};
7676
```
7777

78+
### Other JSON shapes
79+
80+
If your data doesn't follow the `type` | `props` shape, `react-from-json` makes it easy to map your data on the fly using the `mapProp` prop.
81+
82+
```jsx
83+
import React from "react";
84+
import ReactFromJSON from "react-from-json";
85+
import mapping from "./mapping";
86+
87+
const entryWithDifferentShape = {
88+
_type: "Burger",
89+
chain: "Wahlburger",
90+
children: {
91+
_type: "Patty",
92+
variant: "Impossible"
93+
}
94+
};
95+
96+
const mapProp = prop => {
97+
if (prop._type) {
98+
const { _type, ...props } = prop;
99+
100+
return {
101+
type: _type,
102+
props
103+
};
104+
}
105+
106+
return prop;
107+
};
108+
109+
const Example = () => {
110+
return (
111+
<ReactFromJSON
112+
entry={entryWithDifferentShape}
113+
mapping={mapping}
114+
mapProp={mapProp}
115+
/>
116+
);
117+
};
118+
```
119+
78120
### Flat trees
79121

80122
`react-from-json` also supports flat, non-recursive structures via the special `<ComponentLookup />` component. This is useful when working with typed systems like GraphQL, and you need to avoid unions.

src/__helpers__/data.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,34 @@ export const recursiveEntry = {
4141
}
4242
};
4343

44+
export const entryWithDifferentShape = {
45+
_type: "Burger",
46+
chain: "Wahlburger",
47+
bun: {
48+
_type: "Bun",
49+
variant: "sesame"
50+
},
51+
cheese: true,
52+
children: [
53+
{
54+
_type: "Patty",
55+
size: "large",
56+
ingredient: {
57+
_type: "PattyIngredient",
58+
variant: "impossible"
59+
}
60+
},
61+
{
62+
_type: "Patty",
63+
size: "large",
64+
ingredient: {
65+
_type: "PattyIngredient",
66+
variant: "beef"
67+
}
68+
}
69+
]
70+
};
71+
4472
export const flatEntry = {
4573
type: "Burger",
4674
props: {

src/__tests__/__snapshots__/index.tsx.snap

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,48 @@ exports[`ReactFromJSON to render a recursive entry 1`] = `
8989
</div>
9090
</div>
9191
`;
92+
93+
exports[`ReactFromJSON to render a recursive entry with a non-standard shape 1`] = `
94+
<div>
95+
<div />
96+
<div>
97+
<div>
98+
<div />
99+
sesame bun
100+
</div>
101+
</div>
102+
<div>
103+
cheese
104+
</div>
105+
<div>
106+
<div
107+
id="undefined"
108+
>
109+
<div>
110+
0
111+
</div>
112+
large patty
113+
<div>
114+
impossible
115+
</div>
116+
</div>
117+
<div
118+
id="undefined"
119+
>
120+
<div>
121+
Order: 1
122+
</div>
123+
large patty
124+
<div>
125+
beef
126+
</div>
127+
</div>
128+
</div>
129+
<div>
130+
<div>
131+
<div />
132+
sesame bun
133+
</div>
134+
</div>
135+
</div>
136+
`;

src/__tests__/index.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Mapping, Components } from "../__helpers__/interfaces";
66
import {
77
mapping,
88
recursiveEntry,
9+
entryWithDifferentShape,
910
flatEntry,
1011
flatComponents
1112
} from "../__helpers__/data";
@@ -25,6 +26,31 @@ describe("ReactFromJSON", () => {
2526
expect(tree).toMatchSnapshot();
2627
});
2728

29+
it("to render a recursive entry with a non-standard shape", () => {
30+
const tree = renderer
31+
.create(
32+
<BurgerReactFromJSON
33+
entry={entryWithDifferentShape}
34+
mapping={mapping}
35+
mapProp={prop => {
36+
if (prop._type) {
37+
const { _type, ...props } = prop;
38+
39+
return {
40+
type: _type,
41+
props
42+
};
43+
}
44+
45+
return prop;
46+
}}
47+
/>
48+
)
49+
.toJSON();
50+
51+
expect(tree).toMatchSnapshot();
52+
});
53+
2854
it("to render a flat entry with a components attribute", () => {
2955
const tree = renderer
3056
.create(

src/index.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export interface ReactFromJSONProps<
2020
ComponentsType = object
2121
> {
2222
components?: ComponentsType;
23-
entry: Component;
23+
entry: Component | any;
24+
mapProp?: (obj: any) => any;
2425
mapping: MappingType;
2526
}
2627

@@ -71,23 +72,26 @@ class ReactFromJSON<
7172
};
7273

7374
resolveProp = (prop: any, index?: number): any => {
74-
if (prop === null) {
75-
return prop;
76-
} else if (Array.isArray(prop)) {
77-
return prop.map(this.resolveProp);
78-
} else if (typeof prop === "object") {
75+
const { mapProp = (p: any) => p } = this.props;
76+
const mappedProp = mapProp(prop);
77+
78+
if (mappedProp === null) {
79+
return mappedProp;
80+
} else if (Array.isArray(mappedProp)) {
81+
return mappedProp.map(this.resolveProp);
82+
} else if (typeof mappedProp === "object") {
7983
if (
8084
// Typeguard
81-
prop["type"] !== undefined &&
82-
prop["props"] !== undefined
85+
mappedProp["type"] !== undefined &&
86+
mappedProp["props"] !== undefined
8387
) {
84-
const component: Component = prop;
88+
const component: Component = mappedProp;
8589

8690
return this.renderComponent(component, index);
8791
}
8892
}
8993

90-
return prop;
94+
return mappedProp;
9195
};
9296

9397
getNextKey(type: string, propIndex?: number) {
@@ -97,7 +101,7 @@ class ReactFromJSON<
97101
return `${type}_${this.counter[type]++}${propIndexKey}`;
98102
}
99103

100-
renderComponent(component: Component, propIndex?: number) {
104+
renderComponent(component: Component | any, propIndex?: number) {
101105
const { mapping } = this.props;
102106
const { type, props } = component;
103107
const resolvedProps = {};
@@ -126,7 +130,7 @@ class ReactFromJSON<
126130
render() {
127131
const { entry } = this.props;
128132

129-
return <>{this.renderComponent(entry)}</>;
133+
return <>{this.resolveProp(entry)}</>;
130134
}
131135
}
132136

0 commit comments

Comments
 (0)