Repository: react-hook-form/resolvers
Pull request #746 (2025-02-21 16:09:35)

fix(standard-schema): Propertly handle object path segments 
In StandardSchema issues, path segments can either be keys or an object with a `.key` value.
Current handling just calls `path.join('.')`, which would result in `"object Object"` if any path segments are objects.

The Standard Schema team have `@standard-schema/utils` which includes a `getDotPath` that handles segments correctly, so I've used that.


Patch:
diff --git a/bun.lock b/bun.lock
index b6d9c48..53d1226 100644
--- a/bun.lock
+++ b/bun.lock
@@ -9,6 +9,7 @@
       "devDependencies": {
         "@sinclair/typebox": "^0.34.15",
         "@standard-schema/spec": "^1.0.0",
+        "@standard-schema/utils": "^0.3.0",
         "@testing-library/dom": "^10.4.0",
         "@testing-library/jest-dom": "^6.6.3",
         "@testing-library/react": "^16.2.0",
@@ -412,6 +413,8 @@
 
     "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
 
+    "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
+
     "@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="],
 
     "@testing-library/dom": ["@testing-library/dom@10.4.0", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" } }, "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ=="],
diff --git a/package.json b/package.json
index 55b9500..9553d97 100644
--- a/package.json
+++ b/package.json
@@ -269,6 +269,7 @@
   "devDependencies": {
     "@sinclair/typebox": "^0.34.15",
     "@standard-schema/spec": "^1.0.0",
+    "@standard-schema/utils": "^0.3.0",
     "@testing-library/dom": "^10.4.0",
     "@testing-library/jest-dom": "^6.6.3",
     "@testing-library/react": "^16.2.0",
diff --git a/standard-schema/package.json b/standard-schema/package.json
index de255f3..b67ce00 100644
--- a/standard-schema/package.json
+++ b/standard-schema/package.json
@@ -13,6 +13,7 @@
   "peerDependencies": {
     "react-hook-form": "^7.0.0",
     "@standard-schema/spec": "^1.0.0",
+    "@standard-schema/utils": "^0.3.0",
     "@hookform/resolvers": "^2.0.0"
   }
 }
diff --git a/standard-schema/src/standard-schema.ts b/standard-schema/src/standard-schema.ts
index 979f640..8d216ba 100644
--- a/standard-schema/src/standard-schema.ts
+++ b/standard-schema/src/standard-schema.ts
@@ -1,5 +1,6 @@
 import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
 import { StandardSchemaV1 } from '@standard-schema/spec';
+import { getDotPath } from '@standard-schema/utils';
 import { FieldError, FieldValues, Resolver } from 'react-hook-form';
 
 function parseIssues(
@@ -10,7 +11,7 @@ function parseIssues(
 
   for (let i = 0; i < issues.length; i++) {
     const error = issues[i];
-    const path = error.path?.join('.');
+    const path = getDotPath(error);
 
     if (path) {
       if (!errors[path]) {
@@ -52,15 +53,14 @@ function parseIssues(
  * ```
  */
 export function standardSchemaResolver<
-  TFieldValues extends FieldValues,
-  Schema extends StandardSchemaV1<TFieldValues, any>,
+  Schema extends StandardSchemaV1<FieldValues>,
 >(
   schema: Schema,
   resolverOptions: {
     raw?: boolean;
   } = {},
-): Resolver<NonNullable<(typeof schema)['~standard']['types']>['output']> {
-  return async (values: TFieldValues, _, options) => {
+): Resolver<StandardSchemaV1.InferOutput<Schema>> {
+  return async (values, _, options) => {
     let result = schema['~standard'].validate(values);
     if (result instanceof Promise) {
       result = await result;


Test patch:
diff --git a/standard-schema/src/__tests__/__fixtures__/data.ts b/standard-schema/src/__tests__/__fixtures__/data.ts
index e37f521..a71f4c5 100644
--- a/standard-schema/src/__tests__/__fixtures__/data.ts
+++ b/standard-schema/src/__tests__/__fixtures__/data.ts
@@ -1,3 +1,4 @@
+import { StandardSchemaV1 } from '@standard-schema/spec';
 import { Field, InternalFieldName } from 'react-hook-form';
 import { z } from 'zod';
 
@@ -86,3 +87,25 @@ export const fields: Record<InternalFieldName, Field['_f']> = {
     name: 'birthday',
   },
 };
+
+export const customSchema: StandardSchemaV1<
+  StandardSchemaV1.InferInput<typeof schema>,
+  StandardSchemaV1.InferOutput<typeof schema>
+> = {
+  '~standard': {
+    version: 1,
+    vendor: 'custom',
+    validate: () => ({
+      issues: [
+        {
+          path: [{ key: 'username' }],
+          message: 'Custom error',
+        },
+        {
+          path: [{ key: 'like' }, { key: 0 }, { key: 'id' }],
+          message: 'Custom error',
+        },
+      ],
+    }),
+  },
+};
diff --git a/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap b/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap
index f8a68ff..2dba659 100644
--- a/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap
+++ b/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap
@@ -1,5 +1,29 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`standardSchemaResolver > should correctly handle path segments that are objects 1`] = `
+{
+  "errors": {
+    "like": [
+      {
+        "id": {
+          "message": "Custom error",
+          "ref": undefined,
+          "type": "",
+        },
+      },
+    ],
+    "username": {
+      "message": "Custom error",
+      "ref": {
+        "name": "username",
+      },
+      "type": "",
+    },
+  },
+  "values": {},
+}
+`;
+
 exports[`standardSchemaResolver > should return a single error from standardSchemaResolver when validation fails 1`] = `
 {
   "errors": {
diff --git a/standard-schema/src/__tests__/standard-schema.ts b/standard-schema/src/__tests__/standard-schema.ts
index b99bfc1..46186b6 100644
--- a/standard-schema/src/__tests__/standard-schema.ts
+++ b/standard-schema/src/__tests__/standard-schema.ts
@@ -1,5 +1,11 @@
 import { standardSchemaResolver } from '..';
-import { fields, invalidData, schema, validData } from './__fixtures__/data';
+import {
+  customSchema,
+  fields,
+  invalidData,
+  schema,
+  validData,
+} from './__fixtures__/data';
 
 const shouldUseNativeValidation = false;
 
@@ -53,4 +59,16 @@ describe('standardSchemaResolver', () => {
     expect(validateSpy).toHaveBeenCalledTimes(1);
     expect(result).toMatchSnapshot();
   });
+  it('should correctly handle path segments that are objects', async () => {
+    const result = await standardSchemaResolver(customSchema)(
+      validData,
+      undefined,
+      {
+        fields,
+        shouldUseNativeValidation,
+      },
+    );
+
+    expect(result).toMatchSnapshot();
+  });
 });