├── .babelrc.json
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .storybook
    ├── jsonViewTheme.ts
    ├── main.ts
    ├── manager.ts
    └── preview.ts
├── .travis.yml
├── .yarn
    └── releases
    │   └── yarn-3.6.3.cjs
├── .yarnrc.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src
    ├── .eslintrc
    ├── DataRenderer.test.tsx
    ├── DataRenderer.tsx
    ├── DataTypeDetection.test.ts
    ├── DataTypeDetection.ts
    ├── index.test.tsx
    ├── index.tsx
    ├── react-app-env.d.ts
    ├── stories
    │   └── JsonView.stories.tsx
    ├── styles.module.css
    └── typings.d.ts
├── tsconfig.json
├── tsconfig.test.json
└── yarn.lock
/.babelrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "sourceType": "unambiguous",
 3 |   "presets": [
 4 |     [
 5 |       "@babel/preset-env",
 6 |       {
 7 |         "targets": {
 8 |           "chrome": 100,
 9 |           "safari": 15,
10 |           "firefox": 91
11 |         }
12 |       }
13 |     ],
14 |     "@babel/preset-typescript",
15 |     "@babel/preset-react"
16 |   ],
17 |   "plugins": []
18 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
 1 | root = true
 2 | 
 3 | [*]
 4 | charset = utf-8
 5 | indent_style = space
 6 | indent_size = 2
 7 | end_of_line = lf
 8 | insert_final_newline = true
 9 | trim_trailing_whitespace = true
10 | quote_type = single
11 | 
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | node_modules/
4 | .snapshots/
5 | storybook-static/
6 | *.min.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "parser": "@typescript-eslint/parser",
 3 |   "extends": [
 4 |     "standard",
 5 |     "standard-react",
 6 |     "plugin:prettier/recommended",
 7 |     "plugin:@typescript-eslint/eslint-recommended",
 8 |     "plugin:jsx-a11y/recommended"
 9 |   ],
10 |   "env": {
11 |     "node": true
12 |   },
13 |   "parserOptions": {
14 |     "ecmaVersion": 2020,
15 |     "ecmaFeatures": {
16 |       "legacyDecorators": true,
17 |       "jsx": true
18 |     }
19 |   },
20 |   "settings": {
21 |     "react": {
22 |       "version": "detect"
23 |     }
24 |   },
25 |   "rules": {
26 |     "space-before-function-paren": 0,
27 |     "react/prop-types": 0,
28 |     "react/jsx-handler-names": 0,
29 |     "react/jsx-fragments": 0,
30 |     "react/no-unused-prop-types": 0,
31 |     "import/export": 0,
32 |     "no-use-before-define": "off"
33 |   }
34 | }
35 | 
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.yarn/**            linguist-vendored
2 | /.yarn/releases/*    binary
3 | /.yarn/plugins/**/*  binary
4 | /.pnp.*              binary linguist-generated
5 | 
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | 
 2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
 3 | 
 4 | # dependencies
 5 | node_modules
 6 | 
 7 | # builds
 8 | build
 9 | dist
10 | .rpt2_cache
11 | 
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | 
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | coverage
24 | storybook-static
25 | .pnp.*
26 | .yarn/*
27 | !.yarn/patches
28 | !.yarn/plugins
29 | !.yarn/releases
30 | !.yarn/sdks
31 | !.yarn/versions
32 | 
33 | *storybook.log
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | next
2 | .now
3 | dist
4 | public
5 | *.json
6 | *.d.ts
7 | *.yml
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "singleQuote": true,
 3 |   "jsxSingleQuote": true,
 4 |   "semi": true,
 5 |   "tabWidth": 2,
 6 |   "bracketSpacing": true,
 7 |   "jsxBracketSameLine": false,
 8 |   "arrowParens": "always",
 9 |   "trailingComma": "none",
10 |   "printWidth": 100
11 | }
12 | 
--------------------------------------------------------------------------------
/.storybook/jsonViewTheme.ts:
--------------------------------------------------------------------------------
 1 | import { create } from '@storybook/theming';
 2 | 
 3 | export default create({
 4 |   base: 'light',
 5 |   brandTitle: 'JsonView storybook',
 6 |   brandUrl: 'https://anyroad.github.io/react-json-view-lite',
 7 |   brandImage: '',
 8 |   brandTarget: '_self'
 9 | });
10 | 
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
 1 | import type { StorybookConfig } from '@storybook/react-vite';
 2 | 
 3 | const config: StorybookConfig = {
 4 |   stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
 5 | 
 6 |   addons: [
 7 |     '@storybook/addon-onboarding',
 8 |     '@storybook/addon-links',
 9 |     '@storybook/addon-essentials',
10 |     '@chromatic-com/storybook',
11 |     '@storybook/addon-interactions'
12 |   ],
13 | 
14 |   framework: {
15 |     name: '@storybook/react-vite',
16 |     options: {}
17 |   },
18 | 
19 |   core: {
20 |     disableTelemetry: true
21 |   },
22 | 
23 |   docs: {},
24 | 
25 |   typescript: {
26 |     reactDocgen: 'react-docgen-typescript'
27 |   }
28 | };
29 | export default config;
30 | 
--------------------------------------------------------------------------------
/.storybook/manager.ts:
--------------------------------------------------------------------------------
 1 | import { addons } from '@storybook/manager-api';
 2 | import jsonViewTheme from './jsonViewTheme';
 3 | 
 4 | addons.setConfig({
 5 |   isFullscreen: false,
 6 |   showNav: true,
 7 |   showPanel: true,
 8 |   panelPosition: 'bottom',
 9 |   enableShortcuts: true,
10 |   showToolbar: true,
11 |   theme: jsonViewTheme,
12 |   selectedPanel: undefined,
13 |   initialActive: 'sidebar',
14 |   sidebar: {
15 |     showRoots: false,
16 |     collapsedRoots: ['other']
17 |   },
18 |   toolbar: {
19 |     title: { hidden: false },
20 |     zoom: { hidden: false },
21 |     eject: { hidden: false },
22 |     copy: { hidden: false },
23 |     fullscreen: { hidden: false }
24 |   }
25 | });
26 | 
--------------------------------------------------------------------------------
/.storybook/preview.ts:
--------------------------------------------------------------------------------
 1 | import type { Preview } from '@storybook/react';
 2 | 
 3 | const preview: Preview = {
 4 |   parameters: {
 5 |     controls: {
 6 |       matchers: {
 7 |         color: /(background|color)$/i,
 8 |         date: /Date$/i
 9 |       }
10 |     }
11 |   },
12 | 
13 |   tags: ['autodocs']
14 | };
15 | 
16 | export default preview;
17 | 
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
 1 | language: node_js
 2 | before_script:
 3 |   - npm install codecov -g
 4 |   - corepack enable
 5 |   - yarn init -2
 6 | sudo: false
 7 | node_js:
 8 |   - 18
 9 | install:
10 |   - yarn install
11 | script:
12 |   - yarn test
13 |   - codecov -f coverage/*.json
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 | 
3 | yarnPath: .yarn/releases/yarn-3.6.3.cjs
4 | 
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
  1 | ## 2.5.0
  2 | 
  3 | ### New Features
  4 | 
  5 | - `compactTopLevel` property to render an Object without top level "collapse" button and indentation.
  6 | 
  7 | ## 2.4.2
  8 | 
  9 | ### Bug Fixes
 10 | 
 11 | - [beforeExpandChange was not propagated to the child objects](https://github.com/AnyRoad/react-json-view-lite/pull/52)
 12 | 
 13 | ## 2.4.1
 14 | 
 15 | ### Bug Fixes
 16 | 
 17 | - [React warning when rendering empty array or object and then changing the `data` property](https://github.com/AnyRoad/react-json-view-lite/issues/47)
 18 | 
 19 | ## 2.4.0
 20 | 
 21 | ### New Features
 22 | 
 23 | - [adds beforeExpandChange hook property](https://github.com/AnyRoad/react-json-view-lite/issues/39)
 24 | - [adds properties for aria-label of the collapse/expand toggle](https://github.com/AnyRoad/react-json-view-lite/issues/46)
 25 | - [adds style prop to stringify String values to keep escaped characters](https://github.com/AnyRoad/react-json-view-lite/issues/42)
 26 | 
 27 | ## 2.3.0
 28 | 
 29 | ### Bug Fixes
 30 | 
 31 | - [ noQuotesForStringValues not applied to empty string](https://github.com/AnyRoad/react-json-view-lite/issues/45)
 32 | 
 33 | ## 2.2.0
 34 | 
 35 | ### New Features
 36 | 
 37 | - [Officially adds support for the React 19](https://github.com/AnyRoad/react-json-view-lite/pull/43)
 38 | - Adds render for the `function` fields
 39 | 
 40 | ### Bug Fixes
 41 | 
 42 | - [Fixes object type detection](https://github.com/AnyRoad/react-json-view-lite/pull/44)
 43 | 
 44 | ## 2.1.0
 45 | 
 46 | ### New Features
 47 | 
 48 | - Adds separate style for the expandable elements container (`childFieldsContainer`)
 49 | 
 50 | ## 2.0.1
 51 | 
 52 | ### Bug Fixes
 53 | 
 54 | - Fixes margin and padding for the expandable elements (because `div` element changed to the `ul`)
 55 | 
 56 | ## 2.0.0
 57 | 
 58 | Major version upgrade.
 59 | 
 60 | ### Breaking Changes
 61 | 
 62 | - Dropped support of React 16 and React 17. Please use versions 1.x.x if your project uses React 16 or React 17.
 63 | - Expanding and collapsing nodes with the "space" button changed to navigation and expanding using arrow keys. Left and Right to collapse/expand, Up and Down to move to previous/next collapsable element.
 64 | 
 65 | ### New Features
 66 | 
 67 | - [Always quote objects property names with quotesForFieldNames property](https://github.com/AnyRoad/react-json-view-lite/pull/31)
 68 | - [Improved a11y support](https://github.com/AnyRoad/react-json-view-lite/pull/32)
 69 | 
 70 | ## 1.5.0
 71 | 
 72 | ### Bug Fixes
 73 | 
 74 | - [Fixed](https://github.com/AnyRoad/react-json-view-lite/issues/28): Improves empty objects and empty arrays. Also fixes too wide space between two spans having the `punctuation` class (e.g. `] ,` or `[  ]`)
 75 | 
 76 | ## 1.4.0
 77 | 
 78 | ### New Feature
 79 | 
 80 | - [Click on field name to expand node](https://github.com/AnyRoad/react-json-view-lite/pull/27)
 81 | 
 82 | ## 1.3.1
 83 | 
 84 | ### Bug Fixes
 85 | 
 86 | - [Fixed](https://github.com/AnyRoad/react-json-view-lite/issues/24) pressing the spacebar expands/collapses and "pages down" in the browser
 87 | 
 88 | ## 1.3.0
 89 | 
 90 | ### New Feature
 91 | 
 92 | - [New style parameter for not adding double quotes to rendered strings](https://github.com/AnyRoad/react-json-view-lite/issues/22)
 93 | 
 94 | ## 1.2.1
 95 | 
 96 | ### Bug Fixes
 97 | 
 98 | - [Fixed](https://github.com/AnyRoad/react-json-view-lite/issues/20) component didn't work with React 16 and React 17
 99 | 
100 | ## 1.2.0
101 | 
102 | ### New Feature
103 | 
104 | - [Improved accessibility support](https://github.com/AnyRoad/react-json-view-lite/pull/16)
105 | 
106 | ## 1.1.0
107 | 
108 | ### New Feature
109 | 
110 | - [Render Date as an ISO-formatted string](https://github.com/AnyRoad/react-json-view-lite/pull/13)
111 | 
112 | ## 1.0.1
113 | 
114 | ### Bug Fixes
115 | 
116 | - [Fixed](https://github.com/AnyRoad/react-json-view-lite/pull/14) collapse/expand button style
117 | 
118 | ## 1.0.0
119 | 
120 | ### Breaking changes
121 | 
122 | 1. Property `shouldInitiallyExpand` has different name `shouldExpandNode` in order to emphasize that it will be called every time properties change.
123 | 2. If you use custom styles:
124 |    - `pointer` and `expander` are no longer used
125 |    - component uses `collapseIcon`, `expandIcon`, `collapsedContent` styles in order to customize expand/collapse icon and collpased content placeholder which were previously hardcode to the `▸`, `▾` and `...`.
126 |      Default style values use `::after` pseudo-classes to set the content.
127 | 
128 | ## 0.9.8
129 | 
130 | ### Bug Fixes
131 | 
132 | - Fixed [bug when empty object key was not rendered correctly](https://github.com/AnyRoad/react-json-view-lite/issues/9)
133 | 
134 | ## 0.9.7
135 | 
136 | ### New Features
137 | 
138 | - Added [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) rendering support
139 | 
140 | ## 0.9.6
141 | 
142 | ### Bug Fixes
143 | 
144 | - Fixed css style for comma after primitive type value
145 | 
146 | ## 0.9.5
147 | 
148 | ### New Features
149 | 
150 | - Added minimum a11y support
151 | - Added React 18 to peer dependencies
152 | - Updated dev dependencies versions
153 | - Added storybook
154 | 
155 | ## 0.9.4
156 | 
157 | ### New Features
158 | 
159 | - Added ability to expand arrays and nested objects by clicking on the `...` part
160 | - Added `allExpanded` and `collapseAllNested` exported functions which can be used for the `shouldInitiallyExpand` property
161 | - Added new separate style for the "pointer" which applied for `▸`, `▾` and `...`
162 | 
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2024 AnyRoad
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | 
 29 | 
 30 | 
 31 |   react-json-view-lite  is a tiny component for React allowing to render JSON as a tree. It focused on the balance between performance for large JSON inputs and functionality. It might not have all the rich features (suce as customization, copy, json editinng) but still provides more than just rendering json with highlighting - e.g. ability to collapse/expand nested objects and override css. It is written in TypeScript and has no dependencies.
 32 | 
 33 | 
 34 | ## Install
 35 | 
 36 | ```bash
 37 | npm install --save react-json-view-lite
 38 | ```
 39 | 
 40 | ## Version 2.x.x
 41 | 
 42 | Versions 2.x.x supports only React 18 and later. Please use 1.5.0 if your project uses React 16 or 17.
 43 | Also version 2 provides better a11y support, collapsing/expanding and navigation through nested elements using arrow keys ("Space" button does not collapse/expand element anymore), but library size increased about 20%.
 44 | If your project uses custom styles you will need to add the css for the `childFieldsContainer` property like below:
 45 | 
 46 | ```css
 47 | .child-fields-container {
 48 |   margin: 0;
 49 |   padding: 0;
 50 | }
 51 | ```
 52 | 
 53 | because implementation uses `ul` element `div` instead of the elemenent according to the [w3.org example](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/examples/treeview-1a/).
 54 | 
 55 | ## Migration from the 0.9.x versions
 56 | 
 57 | 1. Property `shouldInitiallyExpand` has different name `shouldExpandNode` in order to emphasize that it will be called every time properties change.
 58 | 2. If you use custom styles:
 59 |    - `pointer` and `expander` are no longer used
 60 |    - component uses `collapseIcon`, `expandIcon`, `collapsedContent` styles in order to customize expand/collapse icon and collpased content placeholder which were previously hardcode to the `▸`, `▾` and `...`.
 61 |      Default style values use `::after` pseudo-classes to set the content.
 62 | 
 63 | ## Usage
 64 | 
 65 | ```tsx
 66 | import * as React from 'react';
 67 | 
 68 | import { JsonView, allExpanded, darkStyles, defaultStyles } from 'react-json-view-lite';
 69 | import 'react-json-view-lite/dist/index.css';
 70 | 
 71 | const json = {
 72 |   a: 1,
 73 |   b: 'example'
 74 | };
 75 | 
 76 | const App = () => {
 77 |   return (
 78 |     
 79 |        
 82 |   );
 83 | };
 84 | 
 85 | export default App;
 86 | ```
 87 | 
 88 | Please note that in JavaScript, an anonymous function like `function() {}` or `() => {}` always creates a different function every time component is rendered, so you might need to use
 89 | [useCallback](https://react.dev/reference/react/useCallback) React Hook for the `shouldExpandNode` parameter or extract the function outside the functional component.
 90 | 
 91 | ### StoryBook
 92 | 
 93 | https://anyroad.github.io/react-json-view-lite/
 94 | 
 95 | ### Props
 96 | 
 97 | | Name               | Type                                                     | Default Value | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
 98 | | ------------------ | -------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 99 | | data               | `Object` \| `Array`                                 |               | Data which should be rendered                                                                                                                                                                                                                                                                                                                                                                                                                                 |
100 | | style              | StyleProps                                               | defaultStyles | Optional. CSS classes for rendering. Library provides two build-in implementations: `darkStyles`, `defaultStyles` (see below)                                                                                                                                                                                                                                                                                                                                 |
101 | | shouldExpandNode   | `(level: number, value: any, field?: string) => boolean` | allExpanded   | Optional. Function which will be called during initial rendering for each Object and Array of the data in order to calculate should if this node be expanded. **Note** that this function will be called again to update the each node state once the property value changed. `level` startes from `0`, `field` does not have a value for the array element. Library provides two build-in implementations: `allExpanded` and `collapseAllNested` (see below) |
102 | | clickToExpandNode  | boolean                                                  | false         | Optional. Set to true if you want to expand/collapse nodes by clicking on the node itself.                                                                                                                                                                                                                                                                                                                                                                    |
103 | | beforeExpandChange | (event: NodeExpandingEvent) => boolean                   | undefined     | Optional. Function which will be called before node expanded or collapsed. If the function returns `true` then expand/collapse process goes as usual, if it returns `false` then node state stay the same. For example, you can return `false` to change `shouldExpandNode` property in order to open only desired children nodes.                                                                                                                            |
104 | | compactTopLevel    | boolean                                                  | false         | Optional. Set to true if you do not want to render top level collapse/expand button and indentation. Has no effect if the `data` parameter is not an Object.                                                                                                                                                                                                                                                                                                  |
105 | 
106 | ## interface NodeExpandingEvent
107 | 
108 | | Field Name     | Type    | Description                                                     |
109 | | -------------- | ------- | --------------------------------------------------------------- |
110 | | level          | number  | level of expanded/collapsed node                                |
111 | | value          | any     | Field value (object or array) to be expaneded/collapsed         |
112 | | field          | string? | Field name                                                      |
113 | | newExpandValue | boolean | if node is about to be expanded (`true`) or collapsed (`false`) |
114 | 
115 | ## interface AriaLabels
116 | 
117 | | Field Name   | Type   | Description                                                                             |
118 | | ------------ | ------ | --------------------------------------------------------------------------------------- |
119 | | collapseJson | string | `aria-label` property for the "collapse" node button. Default value is "collapse JSON". |
120 | | expandJson   | string | `aria-label` property for the "expand" node button. Default value is "expand JSON".     |
121 | 
122 | ### Extra exported
123 | 
124 | | Name              | Type                         | Description                                         |
125 | | ----------------- | ---------------------------- | --------------------------------------------------- |
126 | | defaultStyles     | StyleProps                   | Default styles for light background                 |
127 | | darkStyles        | StyleProps                   | Default styles for dark background                  |
128 | | allExpanded       | `() => boolean`              | Always returns `true`                               |
129 | | collapseAllNested | `(level: number) => boolean` | Returns `true` only for the first level (`level=0`) |
130 | 
131 | ### StyleProps
132 | 
133 | | Name                    | Type       | Description                                                                                                                           |
134 | | ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
135 | | container               | string     | CSS class name for rendering parent block                                                                                             |
136 | | childFieldsContainer    | string     | CSS class name for rendering parent block of array or object                                                                          |
137 | | basicChildStyle         | string     | CSS class name for property block containing property name and value                                                                  |
138 | | collapseIcon            | string     | CSS class name for rendering button collapsing Object and Array nodes. Default content is `▾`.                                        |
139 | | expandIcon              | string     | CSS class name for rendering button expanding Object and Array nodes. Default content is `▸`.                                         |
140 | | collapsedContent        | string     | CSS class name for rendering placeholder when Object and Array nodes are collapsed. Default contents is `...`.                        |
141 | | label                   | string     | CSS class name for rendering property names                                                                                           |
142 | | clickableLabel          | string     | CSS class name for rendering clickable property names (requires the `clickToExpandNode` prop to be true)                              |
143 | | nullValue               | string     | CSS class name for rendering null values                                                                                              |
144 | | undefinedValue          | string     | CSS class name for rendering undefined values                                                                                         |
145 | | numberValue             | string     | CSS class name for rendering numeric values                                                                                           |
146 | | stringValue             | string     | CSS class name for rendering string values                                                                                            |
147 | | booleanValue            | string     | CSS class name for rendering boolean values                                                                                           |
148 | | otherValue              | string     | CSS class name for rendering all other values except Object, Arrray, null, undefined, numeric, boolean and string                     |
149 | | punctuation             | string     | CSS class name for rendering `,`, `[`, `]`, `{`, `}`                                                                                  |
150 | | noQuotesForStringValues | boolean    | whether or not to add double quotes when rendering string values, default value is `false`                                            |
151 | | quotesForFieldNames     | boolean    | whether or not to add double quotes when rendering field names, default value is `false`                                              |
152 | | ariaLables              | AriaLables | Text to use for the `aria-label` properties                                                                                           |
153 | | stringifyStringValues   | boolean    | whether or not to call `JSON.stringify` for string values in order to preserve escaped string characters like new line, tab or quotes |
154 | 
155 | ## Comparison with other libraries
156 | 
157 | ### Size and dependencies
158 | 
159 | Here is the size benchmark (using [bundlephobia.com](https://bundlephobia.com)) against similar React libraries (found by https://www.npmjs.com/search?q=react%20json&ranking=popularity):
160 | 
161 | | Library                  | Bundle size                                                                                                                                  | Bundle size (gzip)                                                                                                                              | Dependencies                                                                                                                                              |
162 | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
163 | | **react-json-view-lite** | [](https://bundlephobia.com/result?p=react-json-view-lite)  | [](https://bundlephobia.com/result?p=react-json-view-lite)  | [](https://bundlephobia.com/result?p=react-json-view-lite)  |
164 | | react-json-pretty        | [](https://bundlephobia.com/result?p=react-json-pretty)           | [](https://bundlephobia.com/result?p=react-json-pretty)           | [](https://bundlephobia.com/result?p=react-json-pretty)           |
165 | | react-json-inspector     | [](https://bundlephobia.com/result?p=react-json-inspector)     | [](https://bundlephobia.com/result?p=react-json-inspector)     | [](https://bundlephobia.com/result?p=react-json-inspector)     |
166 | | react-json-tree          | [](https://bundlephobia.com/result?p=react-json-tree)               | [](https://bundlephobia.com/result?p=react-json-tree)               | [](https://bundlephobia.com/result?p=react-json-tree)               |
167 | | react-json-view          | [](https://bundlephobia.com/result?p=react-json-view)               | [](https://bundlephobia.com/result?p=react-json-view)               | [](https://bundlephobia.com/result?p=react-json-view)               |
168 | | react-json-tree-viewer   | [](https://bundlephobia.com/result?p=react-json-tree-viewer) | [](https://bundlephobia.com/result?p=react-json-tree-viewer) | [](https://bundlephobia.com/result?p=react-json-tree-viewer) |
169 | 
170 | ### Performance
171 | 
172 | Performance was mesaured using the [react-component-benchmark](https://github.com/paularmstrong/react-component-benchmark) library. Every component was rendered 50 times using the [300Kb json file](https://github.com/AnyRoad/react-json-view-lite-benchmark/blob/main/src/hugeJson.json) as data source, please refer to source code of the [benchmark project](https://github.com/AnyRoad/react-json-view-lite-benchmark).
173 | All numbers are in milliseconds. Tests were performed on Macbook Air M1 16Gb RAM usging Chrome v96.0.4664.110(official build, arm64). Every component was tested 2 times but there was no significant differences in the results.
174 | 
175 | | Library                  | Min   | Max   | Average | Median | P90   |
176 | | ------------------------ | ----- | ----- | ------- | ------ | ----- |
177 | | **react-json-view-lite** | 81    | 604   | 195     | 82     | 582   |
178 | | react-json-pretty        | 22    | 59    | 32      | 24     | 56    |
179 | | react-json-inspector     | 682   | 1 109 | 758     | 711    | 905   |
180 | | react-json-tree          | 565   | 1 217 | 658     | 620    | 741   |
181 | | react-json-view          | 1 403 | 1 722 | 1529    | 1 540  | 1 631 |
182 | | react-json-tree-viewer   | 266   | 663   | 320     | 278    | 455   |
183 | 
184 | As you can see `react-json-pretty` renders faster than other libraries but it does not have ability to collapse/expand nested objects so it might be good choice if you need just json syntax highlighting.
185 | 
186 | ## License
187 | 
188 | MIT © [AnyRoad](https://github.com/AnyRoad)
189 | 
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "name": "react-json-view-lite",
  3 |   "version": "2.5.0",
  4 |   "description": "JSON viewer component for React focused on performance for large volume input while still providing few customiziation features",
  5 |   "homepage": "https://github.com/AnyRoad/react-json-view-lite",
  6 |   "author": "AnyRoad",
  7 |   "license": "MIT",
  8 |   "keywords": [
  9 |     "react",
 10 |     "json",
 11 |     "component",
 12 |     "view",
 13 |     "json-view",
 14 |     "json-tree",
 15 |     "lite"
 16 |   ],
 17 |   "repository": "AnyRoad/react-json-view-lite",
 18 |   "main": "dist/index.js",
 19 |   "types": "./dist/index.d.ts",
 20 |   "module": "dist/index.modern.js",
 21 |   "source": "src/index.tsx",
 22 |   "engines": {
 23 |     "node": ">=18"
 24 |   },
 25 |   "scripts": {
 26 |     "build": "microbundle-crl --no-compress --format modern,cjs",
 27 |     "start": "microbundle-crl watch --no-compress --format modern,cjs",
 28 |     "prepare": "run-s build",
 29 |     "test": "run-s test:unit test:lint test:build",
 30 |     "test:build": "run-s build",
 31 |     "test:lint": "eslint .",
 32 |     "test:unit": "cross-env CI=1 jest --env=jsdom --coverage",
 33 |     "predeploy": "npm run build-storybook",
 34 |     "deploy-storybook": "gh-pages -d storybook-static",
 35 |     "storybook": "storybook dev -p 6006",
 36 |     "build-storybook": "storybook build"
 37 |   },
 38 |   "peerDependencies": {
 39 |     "react": "^18.0.0 || ^19.0.0"
 40 |   },
 41 |   "devDependencies": {
 42 |     "@babel/preset-typescript": "^7.24.7",
 43 |     "@chromatic-com/storybook": "^3.2.3",
 44 |     "@storybook/addon-essentials": "^8.6.4",
 45 |     "@storybook/addon-interactions": "^8.6.4",
 46 |     "@storybook/addon-links": "^8.6.4",
 47 |     "@storybook/blocks": "^8.6.4",
 48 |     "@storybook/manager-api": "^8.6.4",
 49 |     "@storybook/react": "^8.6.4",
 50 |     "@storybook/react-vite": "^8.6.4",
 51 |     "@storybook/test": "^8.6.4",
 52 |     "@storybook/theming": "^8.6.4",
 53 |     "@testing-library/jest-dom": "^6.5.0",
 54 |     "@testing-library/react": "^16.0.1",
 55 |     "@testing-library/user-event": "^14.5.0",
 56 |     "@types/jest": "29.5.13",
 57 |     "@types/node": "18.19.50",
 58 |     "@types/react": "18.3.5",
 59 |     "@types/react-dom": "18.2.7",
 60 |     "@typescript-eslint/eslint-plugin": "^8.7.0",
 61 |     "@typescript-eslint/parser": "^8.7.0",
 62 |     "cross-env": "7.0.3",
 63 |     "eslint": "8.57.0",
 64 |     "eslint-config-prettier": "9.1.0",
 65 |     "eslint-config-standard": "17.1.0",
 66 |     "eslint-config-standard-react": "13.0.0",
 67 |     "eslint-plugin-import": "2.30.0",
 68 |     "eslint-plugin-jsx-a11y": "^6.10.0",
 69 |     "eslint-plugin-n": "^17.10.0",
 70 |     "eslint-plugin-node": "11.1.0",
 71 |     "eslint-plugin-prettier": "^5.2.0",
 72 |     "eslint-plugin-promise": "^7.1.0",
 73 |     "eslint-plugin-react": "^7.36.0",
 74 |     "eslint-plugin-react-hooks": "^4.6.2",
 75 |     "eslint-plugin-standard": "5.0.0",
 76 |     "eslint-plugin-storybook": "^0.11.2",
 77 |     "gh-pages": "6.1.1",
 78 |     "identity-obj-proxy": "^3.0.0",
 79 |     "jest": "^29.7.0",
 80 |     "jest-environment-jsdom": "^29.7.0",
 81 |     "microbundle-crl": "0.13.11",
 82 |     "npm-run-all": "4.1.5",
 83 |     "postcss": "^8.4.47",
 84 |     "prettier": "3.3.3",
 85 |     "react": "^18.3.1",
 86 |     "react-dom": "^18.3.1",
 87 |     "rollup-jest": "^3.1.0",
 88 |     "storybook": "^8.6.4",
 89 |     "ts-jest": "29.1.2",
 90 |     "typescript": "5.6.3"
 91 |   },
 92 |   "files": [
 93 |     "dist"
 94 |   ],
 95 |   "jest": {
 96 |     "collectCoverageFrom": [
 97 |       "src/*.{ts,tsx}",
 98 |       "!/node_modules/"
 99 |     ],
100 |     "moduleNameMapper": {
101 |       "^.+\\.(css|less|scss)$": "identity-obj-proxy"
102 |     }
103 |   },
104 |   "packageManager": "yarn@3.6.3",
105 |   "eslintConfig": {
106 |     "extends": [
107 |       "plugin:storybook/recommended"
108 |     ]
109 |   }
110 | }
111 | 
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 |   "env": {
3 |     "jest": true
4 |   }
5 | }
6 | 
--------------------------------------------------------------------------------
/src/DataRenderer.test.tsx:
--------------------------------------------------------------------------------
  1 | import * as React from 'react';
  2 | import DataRender, { JsonRenderProps } from './DataRenderer';
  3 | import { allExpanded, collapseAllNested, defaultStyles } from './index';
  4 | import { render, screen, fireEvent } from '@testing-library/react';
  5 | import '@testing-library/jest-dom';
  6 | 
  7 | const commonProps: Omit, 'outerRef'> = {
  8 |   lastElement: false,
  9 |   level: 0,
 10 |   style: {
 11 |     container: '',
 12 |     childFieldsContainer: '',
 13 |     basicChildStyle: '',
 14 |     label: '',
 15 |     clickableLabel: defaultStyles.clickableLabel,
 16 |     nullValue: '',
 17 |     undefinedValue: '',
 18 |     numberValue: '',
 19 |     stringValue: '',
 20 |     booleanValue: '',
 21 |     otherValue: '',
 22 |     punctuation: '',
 23 |     expandIcon: defaultStyles.expandIcon,
 24 |     collapseIcon: defaultStyles.collapseIcon,
 25 |     collapsedContent: defaultStyles.collapsedContent,
 26 |     noQuotesForStringValues: false,
 27 |     ariaLables: {
 28 |       expandJson: 'expand',
 29 |       collapseJson: 'collapse'
 30 |     },
 31 |     stringifyStringValues: false
 32 |   },
 33 |   shouldExpandNode: allExpanded,
 34 |   clickToExpandNode: false,
 35 |   value: undefined,
 36 |   field: undefined
 37 | };
 38 | 
 39 | const WrappedDataRenderer = (testProps: Partial>) => {
 40 |   const ref = React.useRef(null);
 41 |   return (
 42 |     
 43 |       
 45 |   );
 46 | };
 47 | 
 48 | const NoRefWrappedDataRenderer = (testProps: Partial>) => {
 49 |   const ref = React.useRef(null);
 50 |   return (
 51 |     
 52 |       
 54 |   );
 55 | };
 56 | 
 57 | const collapseAll = () => false;
 58 | 
 59 | const testButtonsCollapsed = () => {
 60 |   const buttons = screen.getAllByRole('button', { hidden: true });
 61 |   expect(buttons).toHaveLength(1);
 62 |   expect(buttons[0]).toHaveClass('expand-icon-light');
 63 |   expect(buttons[0]).not.toHaveClass('collapse-icon-light');
 64 |   expect(buttons[0]).toHaveAttribute('aria-expanded', 'false');
 65 |   return buttons;
 66 | };
 67 | 
 68 | const testButtonsExpanded = () => {
 69 |   const buttons = screen.getAllByRole('button', { hidden: true });
 70 |   expect(buttons).toHaveLength(1);
 71 |   expect(buttons[0]).toHaveClass('collapse-icon-light');
 72 |   expect(buttons[0]).not.toHaveClass('expand-icon-light');
 73 |   expect(buttons[0]).toHaveAttribute('aria-expanded', 'true');
 74 |   return buttons;
 75 | };
 76 | 
 77 | const testButtonsIfEmpty = () => {
 78 |   expect(() => {
 79 |     screen.getAllByRole('button', { hidden: true });
 80 |   }).toThrow();
 81 | };
 82 | 
 83 | describe('DataRender', () => {
 84 |   it('should render booleans: true', () => {
 85 |     render( {} }} />);
174 |     expect(screen.getByText(/func:/)).toBeInTheDocument();
175 |     expect(screen.getByText('function() { }')).toBeInTheDocument();
176 |   });
177 | 
178 |   it('should render dates', () => {
179 |     render( false}
536 |       />
537 |     );
538 |     expect(screen.queryByText(/obj/)).not.toBeInTheDocument();
539 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
540 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
541 | 
542 |     testButtonsCollapsed();
543 |     const collapsedContent = container.getElementsByClassName(commonProps.style.collapsedContent);
544 |     fireEvent.click(collapsedContent[0]);
545 |     testButtonsCollapsed();
546 |     expect(screen.queryByText(/obj/)).not.toBeInTheDocument();
547 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
548 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
549 |   });
550 | 
551 |   it('should continue expanding if beforeExpandChange returned true', () => {
552 |     const { container } = render(
553 |        true}
557 |       />
558 |     );
559 |     expect(screen.queryByText(/obj/)).not.toBeInTheDocument();
560 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
561 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
562 | 
563 |     testButtonsCollapsed();
564 |     const collapsedContent = container.getElementsByClassName(commonProps.style.collapsedContent);
565 |     fireEvent.click(collapsedContent[0]);
566 |     expect(screen.getByText(/obj/)).toBeInTheDocument();
567 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
568 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
569 |   });
570 | 
571 |   it('should stop expanding for the nested object if beforeExpandChange returned true', () => {
572 |     const { container } = render(
573 |        false}
577 |       />
578 |     );
579 |     expect(screen.queryByText(/obj/)).toBeInTheDocument();
580 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
581 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
582 | 
583 |     const collapsedContent = container.getElementsByClassName(commonProps.style.collapsedContent);
584 |     fireEvent.click(collapsedContent[0]);
585 |     expect(screen.getByText(/obj/)).toBeInTheDocument();
586 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
587 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
588 |   });
589 | 
590 |   it('should stop expanding if beforeExpandChange returned false and render with new shouldExpandNode value', () => {
591 |     let level = null;
592 |     let field = null;
593 |     let value = null;
594 |     let expanded = null;
595 |     const inputData = { obj: { test: 123 } };
596 | 
597 |     const { container, rerender } = render(
598 |        {
602 |           level = event.level;
603 |           field = event.field;
604 |           value = event.value;
605 |           expanded = event.newExpandValue;
606 |           return false;
607 |         }}
608 |       />
609 |     );
610 |     expect(screen.queryByText(/obj/)).not.toBeInTheDocument();
611 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
612 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
613 | 
614 |     testButtonsCollapsed();
615 |     const collapsedContent = container.getElementsByClassName(commonProps.style.collapsedContent);
616 |     fireEvent.click(collapsedContent[0]);
617 |     testButtonsCollapsed();
618 | 
619 |     expect(level).toBe(0);
620 |     expect(expanded).toBe(true);
621 |     expect(field).toBeUndefined();
622 |     expect(value).toBe(inputData);
623 | 
624 |     rerender(
625 |        false}
629 |       />
630 |     );
631 | 
632 |     expect(screen.getByText(/obj/)).toBeInTheDocument();
633 |     expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
634 |     expect(screen.queryByText('123')).not.toBeInTheDocument();
635 |   });
636 | });
637 | 
--------------------------------------------------------------------------------
/src/DataRenderer.tsx:
--------------------------------------------------------------------------------
  1 | import * as React from 'react';
  2 | import * as DataTypeDetection from './DataTypeDetection';
  3 | import { AriaLabels, NodeExpandingEvent } from '.';
  4 | 
  5 | export interface StyleProps {
  6 |   container: string;
  7 |   basicChildStyle: string;
  8 |   label: string;
  9 |   clickableLabel: string;
 10 |   nullValue: string;
 11 |   undefinedValue: string;
 12 |   numberValue: string;
 13 |   stringValue: string;
 14 |   booleanValue: string;
 15 |   otherValue: string;
 16 |   punctuation: string;
 17 |   expandIcon: string;
 18 |   collapseIcon: string;
 19 |   collapsedContent: string;
 20 |   childFieldsContainer: string;
 21 |   noQuotesForStringValues?: boolean;
 22 |   quotesForFieldNames?: boolean;
 23 |   ariaLables: AriaLabels;
 24 |   stringifyStringValues: boolean;
 25 | }
 26 | 
 27 | interface CommonRenderProps {
 28 |   lastElement: boolean;
 29 |   /** There should only be one node with `level==0`. */
 30 |   level: number;
 31 |   style: StyleProps;
 32 |   shouldExpandNode: (level: number, value: any, field?: string) => boolean;
 33 |   clickToExpandNode: boolean;
 34 |   outerRef: React.RefObject;
 35 |   beforeExpandChange?: (event: NodeExpandingEvent) => boolean;
 36 | }
 37 | 
 38 | export interface JsonRenderProps extends CommonRenderProps {
 39 |   field?: string;
 40 |   value: T;
 41 | }
 42 | 
 43 | export interface ExpandableRenderProps extends CommonRenderProps {
 44 |   field: string | undefined;
 45 |   value: Array | object;
 46 |   data: Array<[string | undefined, any]>;
 47 |   openBracket: string;
 48 |   closeBracket: string;
 49 | }
 50 | 
 51 | // still keep quotes for the field names if it is empty string
 52 | // but do not wrap with quotes for the empty string values
 53 | // in the quoteStringValue function because it can cause
 54 | // double quotes for the custom css styles.
 55 | function quoteString(value: string, quoted = false) {
 56 |   return !value || quoted ? `"${value}"` : value;
 57 | }
 58 | 
 59 | function quoteStringValue(value: string, quoted: boolean, stringify: boolean) {
 60 |   if (stringify) {
 61 |     return JSON.stringify(value);
 62 |   }
 63 |   return quoted ? `"${value}"` : value;
 64 | }
 65 | 
 66 | function ExpandableObject({
 67 |   field,
 68 |   value,
 69 |   data,
 70 |   lastElement,
 71 |   openBracket,
 72 |   closeBracket,
 73 |   level,
 74 |   style,
 75 |   shouldExpandNode,
 76 |   clickToExpandNode,
 77 |   outerRef,
 78 |   beforeExpandChange
 79 | }: ExpandableRenderProps) {
 80 |   // follows tree example for role structure and keypress actions: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/examples/treeview-1a/
 81 | 
 82 |   const shouldExpandNodeCalledRef = React.useRef(false);
 83 |   const [expanded, setExpanded] = React.useState(() => shouldExpandNode(level, value, field));
 84 |   const expanderButtonRef = React.useRef(null);
 85 | 
 86 |   React.useEffect(() => {
 87 |     if (!shouldExpandNodeCalledRef.current) {
 88 |       shouldExpandNodeCalledRef.current = true;
 89 |     } else {
 90 |       setExpanded(shouldExpandNode(level, value, field));
 91 |     }
 92 |     // eslint-disable-next-line react-hooks/exhaustive-deps
 93 |   }, [shouldExpandNode]);
 94 | 
 95 |   const contentsId = React.useId();
 96 | 
 97 |   if (data.length === 0) {
 98 |     return EmptyObject({ field, openBracket, closeBracket, lastElement, style });
 99 |   }
100 | 
101 |   const expanderIconStyle = expanded ? style.collapseIcon : style.expandIcon;
102 |   const ariaLabel = expanded ? style.ariaLables.collapseJson : style.ariaLables.expandJson;
103 |   const childLevel = level + 1;
104 |   const lastIndex = data.length - 1;
105 | 
106 |   const setExpandWithCallback = (newExpandValue: boolean) => {
107 |     if (
108 |       expanded !== newExpandValue &&
109 |       (!beforeExpandChange || beforeExpandChange({ level, value, field, newExpandValue }))
110 |     ) {
111 |       setExpanded(newExpandValue);
112 |     }
113 |   };
114 | 
115 |   const onKeyDown = (e: React.KeyboardEvent) => {
116 |     if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
117 |       e.preventDefault();
118 |       setExpandWithCallback(e.key === 'ArrowRight');
119 |     } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
120 |       e.preventDefault();
121 |       const direction = e.key === 'ArrowUp' ? -1 : 1;
122 | 
123 |       if (!outerRef.current) return;
124 |       const buttonElements = outerRef.current.querySelectorAll('[role=button]');
125 |       let currentIndex = -1;
126 | 
127 |       for (let i = 0; i < buttonElements.length; i++) {
128 |         if (buttonElements[i].tabIndex === 0) {
129 |           currentIndex = i;
130 |           break;
131 |         }
132 |       }
133 |       if (currentIndex < 0) {
134 |         return;
135 |       }
136 | 
137 |       const nextIndex = (currentIndex + direction + buttonElements.length) % buttonElements.length; // auto-wrap
138 |       buttonElements[currentIndex].tabIndex = -1;
139 |       buttonElements[nextIndex].tabIndex = 0;
140 |       buttonElements[nextIndex].focus();
141 |     }
142 |   };
143 | 
144 |   const onClick = () => {
145 |     setExpandWithCallback(!expanded);
146 | 
147 |     const buttonElement = expanderButtonRef.current;
148 |     if (!buttonElement) return;
149 |     const prevButtonElement = outerRef.current?.querySelector(
150 |       '[role=button][tabindex="0"]'
151 |     );
152 |     if (prevButtonElement) {
153 |       prevButtonElement.tabIndex = -1;
154 |     }
155 |     buttonElement.tabIndex = 0;
156 |     buttonElement.focus();
157 |   };
158 | 
159 |   return (
160 |     
166 |       
178 |       {(field || field === '') &&
179 |         (clickToExpandNode ? (
180 |           // don't apply role="button" or tabIndex even though has onClick, because has same
181 |           // function as the +/- expander button (so just expose that button to keyboard and a11y tree)
182 |           // eslint-disable-next-line jsx-a11y/no-static-element-interactions
183 |           
184 |             {quoteString(field, style.quotesForFieldNames)}:
185 |            
186 |         ) : (
187 |           
{quoteString(field, style.quotesForFieldNames)}: 
188 |         ))}
189 |       
{openBracket} 
190 | 
191 |       {expanded ? (
192 |         
193 |           {data.map((dataElement, index) => (
194 |              
208 |       ) : (
209 |         // don't apply role="button" or tabIndex even though has onClick, because has same
210 |         // function as the +/- expander button (so just expose that button to keyboard and a11y tree)
211 |         // eslint-disable-next-line jsx-a11y/no-static-element-interactions
212 |         
213 |       )}
214 | 
215 |       
{closeBracket} 
216 |       {!lastElement && 
, }
217 |     
232 |       {(field || field === '') && (
233 |         {quoteString(field, style.quotesForFieldNames)}: 
234 |       )}
235 |       {openBracket} 
236 |       {closeBracket} 
237 |       {!lastElement && , }
238 |     
239 |   );
240 | }
241 | 
242 | function JsonObject({
243 |   field,
244 |   value,
245 |   style,
246 |   lastElement,
247 |   shouldExpandNode,
248 |   clickToExpandNode,
249 |   level,
250 |   outerRef,
251 |   beforeExpandChange
252 | }: JsonRenderProps) {
253 |   return ExpandableObject({
254 |     field,
255 |     value,
256 |     lastElement: lastElement || false,
257 |     level,
258 |     openBracket: '{',
259 |     closeBracket: '}',
260 |     style,
261 |     shouldExpandNode,
262 |     clickToExpandNode,
263 |     data: Object.keys(value).map((key) => [key, value[key as keyof typeof value]]),
264 |     outerRef,
265 |     beforeExpandChange
266 |   });
267 | }
268 | 
269 | function JsonArray({
270 |   field,
271 |   value,
272 |   style,
273 |   lastElement,
274 |   level,
275 |   shouldExpandNode,
276 |   clickToExpandNode,
277 |   outerRef,
278 |   beforeExpandChange
279 | }: JsonRenderProps>) {
280 |   return ExpandableObject({
281 |     field,
282 |     value,
283 |     lastElement: lastElement || false,
284 |     level,
285 |     openBracket: '[',
286 |     closeBracket: ']',
287 |     style,
288 |     shouldExpandNode,
289 |     clickToExpandNode,
290 |     data: value.map((element) => [undefined, element]),
291 |     outerRef,
292 |     beforeExpandChange
293 |   });
294 | }
295 | 
296 | function JsonPrimitiveValue({
297 |   field,
298 |   value,
299 |   style,
300 |   lastElement
301 | }: JsonRenderProps) {
302 |   let stringValue;
303 |   let valueStyle = style.otherValue;
304 | 
305 |   if (value === null) {
306 |     stringValue = 'null';
307 |     valueStyle = style.nullValue;
308 |   } else if (value === undefined) {
309 |     stringValue = 'undefined';
310 |     valueStyle = style.undefinedValue;
311 |   } else if (DataTypeDetection.isString(value)) {
312 |     stringValue = quoteStringValue(
313 |       value,
314 |       !style.noQuotesForStringValues,
315 |       style.stringifyStringValues
316 |     );
317 |     valueStyle = style.stringValue;
318 |   } else if (DataTypeDetection.isBoolean(value)) {
319 |     stringValue = value ? 'true' : 'false';
320 |     valueStyle = style.booleanValue;
321 |   } else if (DataTypeDetection.isNumber(value)) {
322 |     stringValue = value.toString();
323 |     valueStyle = style.numberValue;
324 |   } else if (DataTypeDetection.isBigInt(value)) {
325 |     stringValue = `${value.toString()}n`;
326 |     valueStyle = style.numberValue;
327 |   } else if (DataTypeDetection.isDate(value)) {
328 |     stringValue = value.toISOString();
329 |   } else if (DataTypeDetection.isFunction(value)) {
330 |     stringValue = 'function() { }';
331 |   } else {
332 |     stringValue = (value as any).toString();
333 |   }
334 | 
335 |   return (
336 |     
337 |       {(field || field === '') && (
338 |         {quoteString(field, style.quotesForFieldNames)}: 
339 |       )}
340 |       {stringValue} 
341 |       {!lastElement && , }
342 |     
343 |   );
344 | }
345 | 
346 | export default function DataRender(props: JsonRenderProps) {
347 |   const value = props.value;
348 |   if (DataTypeDetection.isArray(value)) {
349 |     return  => {
22 |   return Array.isArray(data);
23 | };
24 | 
25 | export const isObject = (data: any): data is object => {
26 |   return typeof data === 'object' && data !== null;
27 | };
28 | 
29 | export const isNull = (data: any): data is null => {
30 |   return data === null;
31 | };
32 | 
33 | export const isUndefined = (data: any): data is undefined => {
34 |   return data === undefined;
35 | };
36 | 
37 | export const isFunction = (data: unknown): data is Function => {
38 |   return !!data && data instanceof Object && typeof data === 'function';
39 | };
40 | 
--------------------------------------------------------------------------------
/src/index.test.tsx:
--------------------------------------------------------------------------------
 1 | import * as React from 'react';
 2 | import { JsonView, defaultStyles, allExpanded, collapseAllNested } from '.';
 3 | import { fireEvent, render, screen } from '@testing-library/react';
 4 | import '@testing-library/jest-dom';
 5 | import { StyleProps } from './DataRenderer';
 6 | 
 7 | describe('JsonView', () => {
 8 |   it('should render object', () => {
 9 |     render(;
 25 |   style?: Partial;
 26 |   shouldExpandNode?: (level: number, value: any, field?: string) => boolean;
 27 |   clickToExpandNode?: boolean;
 28 |   beforeExpandChange?: (event: NodeExpandingEvent) => boolean;
 29 |   compactTopLevel?: boolean;
 30 | }
 31 | 
 32 | export const defaultStyles: StyleProps = {
 33 |   container: styles['container-light'],
 34 |   basicChildStyle: styles['basic-element-style'],
 35 |   childFieldsContainer: styles['child-fields-container'],
 36 |   label: styles['label-light'],
 37 |   clickableLabel: styles['clickable-label-light'],
 38 |   nullValue: styles['value-null-light'],
 39 |   undefinedValue: styles['value-undefined-light'],
 40 |   stringValue: styles['value-string-light'],
 41 |   booleanValue: styles['value-boolean-light'],
 42 |   numberValue: styles['value-number-light'],
 43 |   otherValue: styles['value-other-light'],
 44 |   punctuation: styles['punctuation-light'],
 45 |   collapseIcon: styles['collapse-icon-light'],
 46 |   expandIcon: styles['expand-icon-light'],
 47 |   collapsedContent: styles['collapsed-content-light'],
 48 |   noQuotesForStringValues: false,
 49 |   quotesForFieldNames: false,
 50 |   ariaLables: defaultAriaLables,
 51 |   stringifyStringValues: false
 52 | };
 53 | 
 54 | export const darkStyles: StyleProps = {
 55 |   container: styles['container-dark'],
 56 |   basicChildStyle: styles['basic-element-style'],
 57 |   childFieldsContainer: styles['child-fields-container'],
 58 |   label: styles['label-dark'],
 59 |   clickableLabel: styles['clickable-label-dark'],
 60 |   nullValue: styles['value-null-dark'],
 61 |   undefinedValue: styles['value-undefined-dark'],
 62 |   stringValue: styles['value-string-dark'],
 63 |   booleanValue: styles['value-boolean-dark'],
 64 |   numberValue: styles['value-number-dark'],
 65 |   otherValue: styles['value-other-dark'],
 66 |   punctuation: styles['punctuation-dark'],
 67 |   collapseIcon: styles['collapse-icon-dark'],
 68 |   expandIcon: styles['expand-icon-dark'],
 69 |   collapsedContent: styles['collapsed-content-dark'],
 70 |   noQuotesForStringValues: false,
 71 |   quotesForFieldNames: false,
 72 |   ariaLables: defaultAriaLables,
 73 |   stringifyStringValues: false
 74 | };
 75 | 
 76 | export const allExpanded = () => true;
 77 | export const collapseAllNested = (level: number) => level < 1;
 78 | 
 79 | export const JsonView = ({
 80 |   data,
 81 |   style = defaultStyles,
 82 |   shouldExpandNode = allExpanded,
 83 |   clickToExpandNode = false,
 84 |   beforeExpandChange,
 85 |   compactTopLevel,
 86 |   ...ariaAttrs
 87 | }: Props) => {
 88 |   const outerRef = React.useRef(null);
 89 |   return (
 90 |     
 97 |       {compactTopLevel && isObject(data) ? (
 98 |         Object.entries(data).map(([key, value]) => (
 99 |           
125 |   );
126 | };
127 | 
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | /// 
 37 |         {Story()}
 38 |       
 39 |     )
 40 |   ]
 41 | } as Meta;
 42 | 
 43 | const Template: StoryFn = (args) =>