GillianPerard / typescript-json-serializer

A typescript library to deserialize json into typescript classes and serialize classes into json.
MIT License
212 stars 29 forks source link

[BUG]: Cannot read properties of undefined (reading 'name') #207

Closed fantasyz closed 1 year ago

fantasyz commented 1 year ago

Version

6.0.0

Description

Deserializing giving me error unless I use a custom JsonSerializer with this following line.

formatPropertyName: (propertyName: string) => `_${propertyName}`,

It was working before but suddenly does not work. I reduced and extracted it to a simple component but the problem still occurs. (Tried relaunch "npm start". Tried on different browsers. no luck)

Error

on Edge:

Cannot read properties of undefined (reading 'name')
TypeError: Cannot read properties of undefined (reading 'name')
    at f.deserializeProperty (http://localhost:3000/static/js/bundle.js:49865:245)
    at http://localhost:3000/static/js/bundle.js:49780:21
    at Array.forEach (<anonymous>)
    at f.deserializeObject (http://localhost:3000/static/js/bundle.js:49778:17)
    at test (http://localhost:3000/static/js/bundle.js:3361:32)
    at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:18781:18)
    at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:18825:20)
    at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:18882:35)
    at invokeGuardedCallbackAndCatchFirstError (http://localhost:3000/static/js/bundle.js:18896:29)
    at executeDispatch (http://localhost:3000/static/js/bundle.js:23040:7)

On firefox:

v is undefined
./node_modules/typescript-json-serializer/dist/index.esm.js/f.prototype.deserializeProperty@http://localhost:3000/static/js/bundle.js:49865:200
./node_modules/typescript-json-serializer/dist/index.esm.js/f.prototype.deserializeObject/<@http://localhost:3000/static/js/bundle.js:49780:21
./node_modules/typescript-json-serializer/dist/index.esm.js/f.prototype.deserializeObject@http://localhost:3000/static/js/bundle.js:49778:17
test@http://localhost:3000/static/js/bundle.js:3361:32
callCallback@http://localhost:3000/static/js/bundle.js:18781:18
invokeGuardedCallbackDev@http://localhost:3000/static/js/bundle.js:18825:20
invokeGuardedCallback@http://localhost:3000/static/js/bundle.js:18882:35
invokeGuardedCallbackAndCatchFirstError@http://localhost:3000/static/js/bundle.js:18896:29
executeDispatch@http://localhost:3000/static/js/bundle.js:23040:46
processDispatchQueueItemsInOrder@http://localhost:3000/static/js/bundle.js:23066:26
processDispatchQueue@http://localhost:3000/static/js/bundle.js:23077:41
dispatchEventsForPlugins@http://localhost:3000/static/js/bundle.js:23086:27
./node_modules/react-dom/cjs/react-dom.development.js/dispatchEventForPluginEventSystem/<@http://localhost:3000/static/js/bundle.js:23246:16
batchedUpdates$1@http://localhost:3000/static/js/bundle.js:37638:16
batchedUpdates@http://localhost:3000/static/js/bundle.js:18629:16
dispatchEventForPluginEventSystem@http://localhost:3000/static/js/bundle.js:23245:21
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay@http://localhost:3000/static/js/bundle.js:20751:42
dispatchEvent@http://localhost:3000/static/js/bundle.js:20745:88
dispatchDiscreteEvent@http://localhost:3000/static/js/bundle.js:20722:22
EventListener.handleEvent*addEventBubbleListener@http://localhost:3000/static/js/bundle.js:20944:14
addTrappedEventListener@http://localhost:3000/static/js/bundle.js:23168:33
listenToNativeEvent@http://localhost:3000/static/js/bundle.js:23112:30
./node_modules/react-dom/cjs/react-dom.development.js/listenToAllSupportedEvents/<@http://localhost:3000/static/js/bundle.js:23123:34
listenToAllSupportedEvents@http://localhost:3000/static/js/bundle.js:23118:25
createRoot@http://localhost:3000/static/js/bundle.js:40401:33
createRoot$1@http://localhost:3000/static/js/bundle.js:40747:14
./node_modules/react-dom/client.js/exports.createRoot@http://localhost:3000/static/js/bundle.js:40823:16
./src/index.tsx@http://localhost:3000/static/js/bundle.js:4116:60
options.factory@http://localhost:3000/static/js/bundle.js:59428:31
__webpack_require__@http://localhost:3000/static/js/bundle.js:58852:33
@http://localhost:3000/static/js/bundle.js:60074:56
@http://localhost:3000/static/js/bundle.js:60076:12

Reproduction

The sample code below has a button to trigger the logic inside test().

error will occur unless this line is uncommented when using custom JsonSerializer. However, uncomenting it will fail to read the Id value (20001) and print out result "Apple {Id: 0}".

// formatPropertyName: (propertyName: string) => `_${propertyName}`,

Sample Code:

import React from "react";
import { JsonSerializer, throwError } from "typescript-json-serializer";
import { JsonObject, JsonProperty } from "typescript-json-serializer";

@JsonObject()
class Apple {
  @JsonProperty()
  public Id: number = 0;
}

const Test: React.FC = () => {
  // Fetch the list of users from the server and update the state
  const test = async () => {
    const customSerializer = new JsonSerializer({
      // Throw errors instead of logging
      errorCallback: throwError,

      // Allow all nullish values
      nullishPolicy: {
        //undefined: "allow",
        undefined: "remove",
        null: "allow",
      },

      // Disallow additional properties (non JsonProperty)
      //additionalPropertiesPolicy: "disallow",
      additionalPropertiesPolicy: "remove",

      // e.g. if all the properties in the json object are prefixed by '_'
      // formatPropertyName: (propertyName: string) => `_${propertyName}`,
    });

    var data = { Id: 20001 };
    var json = JSON.stringify(data);
    console.log("Input: " + json);

    var obj = customSerializer.deserializeObject(data, Apple);
    console.log("Result (custom): ");
    console.log(obj);

    // const defaultSerializer = new JsonSerializer();
    // console.log("defaultSerializer: " + JSON.stringify(defaultSerializer));
    // obj = defaultSerializer.deserializeObject(json, Apple);
    // console.log("Result (default):");
    // console.log(obj);
  };

  return (
    <div>
      <button onClick={test}>Test</button>
    </div>
  );
};

export default Test;

On which OS the bug appears?

Windows 10

What is your project type?

React Typescript

On which build mode the bug appears?

Development

Anything else?

No response

GillianPerard commented 1 year ago

Hello!

I tested your code by creating a new next.js (with ts) app and it works both in dev en prod. I can't reproduce your bug.

In your test, if the propertyName function is commented everything works, if you uncomment it fails because the data you provide has no key prefixed by an underscore.

I'm not a react developer but historically, babel did not correctly support the decorators, but now with the new build system (vercel here), it seems there is no problem.

Could you tell me more about the technologies you use for this project?

fantasyz commented 1 year ago

Sure. Here is the setting files:

package.json

{
  "name": "web-client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@babel/runtime": "^7.21.5",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.30",
    "@types/react": "^18.2.6",
    "@types/react-dom": "^18.2.4",
    "@types/react-router-dom": "^5.3.3",
    "bootstrap-dark-5": "^1.1.3",
    "i18next": "^22.5.0",
    "i18next-browser-languagedetector": "^7.0.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-i18next": "^12.3.1",
    "react-router-dom": "^6.11.2",
    "react-scripts": "^5.0.1",
    "typescript": "^4.9.5",
    "typescript-json-serializer": "^6.0.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start --port 5555",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": ["src"]
}
fantasyz commented 1 year ago

btw, it was working at the beginning and it just suddenly stop working. I wonder if it is related to new import or lib update but I did a lots of things during that time so I don't remember what I actually did. Now I am deserializing the classes by hand to get around the issue.

GillianPerard commented 1 year ago

I don't know how it could work before because as far as I remember, babel (used with create-react-app) does not support decorators...

I make it work with 2 solutions but this is not an issue of my lib so you need to modify your project.

Solution 1: Switch to a react framework like Next.js (I tested with it).

Solution 2: You need to do some manipulation:

module.exports = override(addBabelPlugin(babelTsTransformPlugin));

- modify the scripts in your `package.json` to use `react-app-rewired` instead of `react-scripts`
"scripts": {
    "start": "react-app-rewired start --port 5555",
    "build": "react-app-rewired build --production",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
},
fantasyz commented 1 year ago

thanks