diff --git a/packages/core-base/src/ast.ts b/packages/core-base/src/ast.ts
new file mode 100644
index 000000000..98a367aa6
--- /dev/null
+++ b/packages/core-base/src/ast.ts
@@ -0,0 +1,123 @@
+import { NodeTypes } from '@intlify/message-compiler'
+import { hasOwn, isObject } from '@intlify/shared'
+
+import type {
+  LinkedModifierNode,
+  LinkedNode,
+  MessageNode,
+  Node,
+  PluralNode,
+  ResourceNode
+} from '@intlify/message-compiler'
+import type { MessageType } from './runtime'
+
+export function isMessageAST(val: unknown): val is ResourceNode {
+  return (
+    isObject(val) &&
+    resolveType(val as Node) === 0 &&
+    (hasOwn(val, 'b') || hasOwn(val, 'body'))
+  )
+}
+
+const PROPS_BODY = ['b', 'body']
+
+export function resolveBody(node: ResourceNode) {
+  return resolveProps<MessageNode | PluralNode>(node, PROPS_BODY)
+}
+
+const PROPS_CASES = ['c', 'cases']
+
+export function resolveCases(node: PluralNode) {
+  return resolveProps<PluralNode['cases'], PluralNode['cases']>(
+    node,
+    PROPS_CASES,
+    []
+  )
+}
+
+const PROPS_STATIC = ['s', 'static']
+
+export function resolveStatic(node: MessageNode) {
+  return resolveProps(node, PROPS_STATIC)
+}
+
+const PROPS_ITEMS = ['i', 'items']
+
+export function resolveItems(node: MessageNode) {
+  return resolveProps<MessageNode['items'], MessageNode['items']>(
+    node,
+    PROPS_ITEMS,
+    []
+  )
+}
+
+const PROPS_TYPE = ['t', 'type']
+
+export function resolveType(node: Node): ReturnType<typeof resolveProps> {
+  return resolveProps<NodeTypes>(node, PROPS_TYPE)
+}
+
+const PROPS_VALUE = ['v', 'value']
+
+export function resolveValue<Message = string>(
+  node: { v?: MessageType<Message>; value?: MessageType<Message> },
+  type: NodeTypes
+): MessageType<Message> {
+  const resolved = resolveProps<Message>(
+    node as Node,
+    PROPS_VALUE
+  ) as MessageType<Message>
+  if (resolved != null) {
+    return resolved
+  } else {
+    throw createUnhandleNodeError(type)
+  }
+}
+
+const PROPS_MODIFIER = ['m', 'modifier']
+
+export function resolveLinkedModifier(node: LinkedNode) {
+  return resolveProps<LinkedModifierNode>(node, PROPS_MODIFIER)
+}
+
+const PROPS_KEY = ['k', 'key']
+
+export function resolveLinkedKey(node: LinkedNode) {
+  const resolved = resolveProps<LinkedNode['key']>(node, PROPS_KEY)
+  if (resolved) {
+    return resolved
+  } else {
+    throw createUnhandleNodeError(NodeTypes.Linked)
+  }
+}
+
+export function resolveProps<T = string, Default = undefined>(
+  node: Node,
+  props: string[],
+  defaultValue?: Default
+): T | Default {
+  for (let i = 0; i < props.length; i++) {
+    const prop = props[i]
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    if (hasOwn(node, prop) && (node as any)[prop] != null) {
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      return (node as any)[prop] as T
+    }
+  }
+  return defaultValue as Default
+}
+
+export const AST_NODE_PROPS_KEYS = [
+  ...PROPS_BODY,
+  ...PROPS_CASES,
+  ...PROPS_STATIC,
+  ...PROPS_ITEMS,
+  ...PROPS_KEY,
+  ...PROPS_MODIFIER,
+  ...PROPS_VALUE,
+  ...PROPS_TYPE
+]
+
+export function createUnhandleNodeError(type: NodeTypes) {
+  return new Error(`unhandled node type: ${type}`)
+}
diff --git a/packages/core-base/src/compilation.ts b/packages/core-base/src/compilation.ts
index 42fda8348..1d3c6c2c6 100644
--- a/packages/core-base/src/compilation.ts
+++ b/packages/core-base/src/compilation.ts
@@ -5,22 +5,14 @@ import {
   mangle,
   optimize
 } from '@intlify/message-compiler'
-import {
-  create,
-  format,
-  hasOwn,
-  isBoolean,
-  isObject,
-  isString,
-  warn
-} from '@intlify/shared'
-import { format as formatMessage, resolveType } from './format'
+import { create, format, isBoolean, isString, warn } from '@intlify/shared'
+import { isMessageAST } from './ast'
+import { format as formatMessage } from './format'
 
 import type {
   CompileError,
   CompileOptions,
   CompilerResult,
-  Node,
   ResourceNode
 } from '@intlify/message-compiler'
 import type { MessageCompilerContext } from './context'
@@ -41,14 +33,6 @@ export function clearCompileCache(): void {
   compileCache = create()
 }
 
-export function isMessageAST(val: unknown): val is ResourceNode {
-  return (
-    isObject(val) &&
-    resolveType(val as Node) === 0 &&
-    (hasOwn(val, 'b') || hasOwn(val, 'body'))
-  )
-}
-
 function baseCompile(
   message: string,
   options: CompileOptions = {}
diff --git a/packages/core-base/src/format.ts b/packages/core-base/src/format.ts
index 95eb7f770..e474a9836 100644
--- a/packages/core-base/src/format.ts
+++ b/packages/core-base/src/format.ts
@@ -1,8 +1,18 @@
 import { NodeTypes } from '@intlify/message-compiler'
 import { hasOwn, isNumber } from '@intlify/shared'
+import {
+  createUnhandleNodeError,
+  resolveBody,
+  resolveCases,
+  resolveItems,
+  resolveLinkedKey,
+  resolveLinkedModifier,
+  resolveStatic,
+  resolveType,
+  resolveValue
+} from './ast'
 
 import type {
-  LinkedModifierNode,
   LinkedNode,
   ListNode,
   MessageNode,
@@ -53,22 +63,6 @@ export function formatParts<Message = string>(
   }
 }
 
-const PROPS_BODY = ['b', 'body']
-
-function resolveBody(node: ResourceNode) {
-  return resolveProps<MessageNode | PluralNode>(node, PROPS_BODY)
-}
-
-const PROPS_CASES = ['c', 'cases']
-
-function resolveCases(node: PluralNode) {
-  return resolveProps<PluralNode['cases'], PluralNode['cases']>(
-    node,
-    PROPS_CASES,
-    []
-  )
-}
-
 export function formatMessageParts<Message = string>(
   ctx: MessageContext<Message>,
   node: MessageNode
@@ -87,22 +81,6 @@ export function formatMessageParts<Message = string>(
   }
 }
 
-const PROPS_STATIC = ['s', 'static']
-
-function resolveStatic(node: MessageNode) {
-  return resolveProps(node, PROPS_STATIC)
-}
-
-const PROPS_ITEMS = ['i', 'items']
-
-function resolveItems(node: MessageNode) {
-  return resolveProps<MessageNode['items'], MessageNode['items']>(
-    node,
-    PROPS_ITEMS,
-    []
-  )
-}
-
 type NodeValue<Message> = {
   v?: MessageType<Message>
   value?: MessageType<Message>
@@ -160,63 +138,3 @@ export function formatMessagePart<Message = string>(
       throw new Error(`unhandled node on format message part: ${type}`)
   }
 }
-
-const PROPS_TYPE = ['t', 'type']
-
-export function resolveType(node: Node): ReturnType<typeof resolveProps> {
-  return resolveProps<NodeTypes>(node, PROPS_TYPE)
-}
-
-const PROPS_VALUE = ['v', 'value']
-
-function resolveValue<Message = string>(
-  node: { v?: MessageType<Message>; value?: MessageType<Message> },
-  type: NodeTypes
-): MessageType<Message> {
-  const resolved = resolveProps<Message>(
-    node as Node,
-    PROPS_VALUE
-  ) as MessageType<Message>
-  if (resolved != null) {
-    return resolved
-  } else {
-    throw createUnhandleNodeError(type)
-  }
-}
-
-const PROPS_MODIFIER = ['m', 'modifier']
-
-function resolveLinkedModifier(node: LinkedNode) {
-  return resolveProps<LinkedModifierNode>(node, PROPS_MODIFIER)
-}
-
-const PROPS_KEY = ['k', 'key']
-
-function resolveLinkedKey(node: LinkedNode) {
-  const resolved = resolveProps<LinkedNode['key']>(node, PROPS_KEY)
-  if (resolved) {
-    return resolved
-  } else {
-    throw createUnhandleNodeError(NodeTypes.Linked)
-  }
-}
-
-function resolveProps<T = string, Default = undefined>(
-  node: Node,
-  props: string[],
-  defaultValue?: Default
-): T | Default {
-  for (let i = 0; i < props.length; i++) {
-    const prop = props[i]
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    if (hasOwn(node, prop) && (node as any)[prop] != null) {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      return (node as any)[prop] as T
-    }
-  }
-  return defaultValue as Default
-}
-
-function createUnhandleNodeError(type: NodeTypes) {
-  return new Error(`unhandled node type: ${type}`)
-}
diff --git a/packages/core-base/src/index.ts b/packages/core-base/src/index.ts
index 0ce608303..5728ce577 100644
--- a/packages/core-base/src/index.ts
+++ b/packages/core-base/src/index.ts
@@ -6,6 +6,7 @@ export type {
   CompileErrorCodes,
   ResourceNode
 } from '@intlify/message-compiler'
+export { AST_NODE_PROPS_KEYS, isMessageAST } from './ast'
 export * from './compilation'
 export * from './context'
 export * from './datetime'
diff --git a/packages/core-base/src/resolver.ts b/packages/core-base/src/resolver.ts
index 68efd8587..1950996f9 100644
--- a/packages/core-base/src/resolver.ts
+++ b/packages/core-base/src/resolver.ts
@@ -1,4 +1,5 @@
 import { isFunction, isObject } from '@intlify/shared'
+import { AST_NODE_PROPS_KEYS, isMessageAST } from './ast'
 
 /** @VueI18nGeneral */
 export type Path = string
@@ -344,7 +345,16 @@ export function resolveValue(obj: unknown, path: Path): PathValue {
   let last = obj
   let i = 0
   while (i < len) {
-    const val = last[hit[i]]
+    const key = hit[i]
+    /**
+     * NOTE:
+     * if `key` is intlify message format AST node key and `last` is intlify message format AST, skip it.
+     * because the AST node is not a key-value structure.
+     */
+    if (AST_NODE_PROPS_KEYS.includes(key) && isMessageAST(last)) {
+      return null
+    }
+    const val = last[key]
     if (val === undefined) {
       return null
     }
diff --git a/packages/core-base/src/translate.ts b/packages/core-base/src/translate.ts
index 5dd58638c..cb739da10 100644
--- a/packages/core-base/src/translate.ts
+++ b/packages/core-base/src/translate.ts
@@ -17,7 +17,7 @@ import {
   measure,
   warn
 } from '@intlify/shared'
-import { isMessageAST } from './compilation'
+import { isMessageAST } from './ast'
 import {
   getAdditionalMeta,
   handleMissing,
diff --git a/packages/core-base/test/compilation.test.ts b/packages/core-base/test/compilation.test.ts
index aaaf13890..4f220d908 100644
--- a/packages/core-base/test/compilation.test.ts
+++ b/packages/core-base/test/compilation.test.ts
@@ -8,7 +8,8 @@ vi.mock('@intlify/shared', async () => {
 })
 
 import { baseCompile } from '@intlify/message-compiler'
-import { clearCompileCache, compile, isMessageAST } from '../src/compilation'
+import { isMessageAST } from '../src/ast'
+import { clearCompileCache, compile } from '../src/compilation'
 import { createMessageContext as context } from '../src/runtime'
 
 const DEFAULT_CONTEXT = { locale: 'en', key: 'key' }
diff --git a/packages/core-base/test/fixtures/ast.ts b/packages/core-base/test/fixtures/ast.ts
new file mode 100644
index 000000000..e616e0134
--- /dev/null
+++ b/packages/core-base/test/fixtures/ast.ts
@@ -0,0 +1,126 @@
+export const ast = {
+  language: {
+    type: 0,
+    start: 0,
+    end: 9,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 10, offset: 9 },
+      source: 'Languages'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 9,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 10, offset: 9 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 9,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 10, offset: 9 }
+          }
+        }
+      ],
+      static: 'Languages'
+    }
+  },
+  product: {
+    type: 0,
+    start: 0,
+    end: 7,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 8, offset: 7 },
+      source: 'Product'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 7,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 8, offset: 7 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 7,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 8, offset: 7 }
+          }
+        }
+      ],
+      static: 'Product'
+    }
+  },
+  'product.type': {
+    type: 0,
+    start: 0,
+    end: 12,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 13, offset: 12 },
+      source: 'Product type'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 12,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 13, offset: 12 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 12,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 13, offset: 12 }
+          }
+        }
+      ],
+      static: 'Product type'
+    }
+  },
+  'product.test.type': {
+    type: 0,
+    start: 0,
+    end: 17,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 18, offset: 17 },
+      source: 'Product test type'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 17,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 18, offset: 17 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 17,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 18, offset: 17 }
+          }
+        }
+      ],
+      static: 'Product test type'
+    }
+  }
+}
diff --git a/packages/core-base/test/resolver.test.ts b/packages/core-base/test/resolver.test.ts
index 13285113f..a7ef5e574 100644
--- a/packages/core-base/test/resolver.test.ts
+++ b/packages/core-base/test/resolver.test.ts
@@ -1,4 +1,5 @@
 import { parse, resolveValue } from '../src/resolver'
+import { ast } from './fixtures/ast'
 
 test('parse', () => {
   expect(parse('a')).toEqual(['a'])
@@ -89,40 +90,51 @@ test('parse', () => {
   expect(parse('🌐.🌐')).toEqual(['🌐', '🌐'])
 })
 
-test('resolveValue', () => {
-  // primitive
-  expect(resolveValue({ a: { b: 1 } }, 'a.b')).toEqual(1)
-  // whitespace
-  expect(resolveValue({ 'a c': 1 }, 'a c')).toEqual(1)
-  expect(resolveValue({ 'a\tc': 1 }, 'a\tc')).toEqual(null)
-  // object
-  expect(resolveValue({ a: { b: 1 } }, 'a')).toEqual({ b: 1 })
-  expect(resolveValue({ a: { 'b c d': 1 } }, 'a.b c d')).toEqual(1)
-  // number key in object
-  expect(
-    resolveValue({ errors: { '1': 'error number 1' } }, 'errors[1]')
-  ).toEqual('error number 1')
-  // array index path
-  expect(resolveValue({ errors: ['error number 0'] }, 'errors[0]')).toEqual(
-    'error number 0'
-  )
-  // array path
-  expect(resolveValue({ errors: ['error number 0'] }, 'errors')).toEqual([
-    'error number 0'
-  ])
-  // not found
-  expect(resolveValue({}, 'a.b')).toEqual(null)
-  // object primitive
-  expect(resolveValue(10, 'a.b')).toEqual(null)
-  // object null
-  expect(resolveValue(null, 'a.b')).toEqual(null)
-  // blanket term
-  expect(resolveValue({}, 'a.b.c[]')).toEqual(null)
-  // blanket middle
-  expect(resolveValue({}, 'a.b.c[]d')).toEqual(null)
-  // function
-  const fn = () => 1
-  expect(resolveValue({ a: fn }, 'a.name')).toEqual(null)
-  expect(resolveValue({ a: fn }, 'a.toString')).toEqual(null)
-  expect(resolveValue({ a: fn }, 'a')).toEqual(fn)
+describe('resolveValue', () => {
+  test('basic', () => {
+    // primitive
+    expect(resolveValue({ a: { b: 1 } }, 'a.b')).toEqual(1)
+    // whitespace
+    expect(resolveValue({ 'a c': 1 }, 'a c')).toEqual(1)
+    expect(resolveValue({ 'a\tc': 1 }, 'a\tc')).toEqual(null)
+    // object
+    expect(resolveValue({ a: { b: 1 } }, 'a')).toEqual({ b: 1 })
+    expect(resolveValue({ a: { 'b c d': 1 } }, 'a.b c d')).toEqual(1)
+    // number key in object
+    expect(
+      resolveValue({ errors: { '1': 'error number 1' } }, 'errors[1]')
+    ).toEqual('error number 1')
+    // array index path
+    expect(resolveValue({ errors: ['error number 0'] }, 'errors[0]')).toEqual(
+      'error number 0'
+    )
+    // array path
+    expect(resolveValue({ errors: ['error number 0'] }, 'errors')).toEqual([
+      'error number 0'
+    ])
+    // not found
+    expect(resolveValue({}, 'a.b')).toEqual(null)
+    // object primitive
+    expect(resolveValue(10, 'a.b')).toEqual(null)
+    // object null
+    expect(resolveValue(null, 'a.b')).toEqual(null)
+    // blanket term
+    expect(resolveValue({}, 'a.b.c[]')).toEqual(null)
+    // blanket middle
+    expect(resolveValue({}, 'a.b.c[]d')).toEqual(null)
+    // function
+    const fn = () => 1
+    expect(resolveValue({ a: fn }, 'a.name')).toEqual(null)
+    expect(resolveValue({ a: fn }, 'a.toString')).toEqual(null)
+    expect(resolveValue({ a: fn }, 'a')).toEqual(fn)
+    // json path
+    expect(resolveValue({ 'a.b': 1 }, 'a.b')).toEqual(null)
+  })
+
+  test('ast', () => {
+    expect(resolveValue(ast, 'language')).toEqual(ast.language)
+    expect(resolveValue(ast, 'product')).toEqual(ast.product)
+    expect(resolveValue(ast, 'product.type')).toEqual(null)
+    expect(resolveValue(ast, 'product.test.type')).toEqual(null)
+  })
 })
diff --git a/packages/core-base/test/translate.test.ts b/packages/core-base/test/translate.test.ts
index f81c9ba36..027172685 100644
--- a/packages/core-base/test/translate.test.ts
+++ b/packages/core-base/test/translate.test.ts
@@ -1,6 +1,7 @@
 /* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/no-explicit-any */
 
 import { baseCompile } from '@intlify/message-compiler'
+import { ast } from './fixtures/ast'
 
 // utils
 import * as shared from '@intlify/shared'
@@ -1024,6 +1025,19 @@ describe('AST passing', () => {
     })
     expect(translate(ctx, 'hi')).toEqual('hi kazupon !')
   })
+
+  test('json path key', () => {
+    const ctx = context({
+      locale: 'en',
+      messages: {
+        en: ast
+      }
+    })
+    expect(translate(ctx, 'languages')).toEqual('languages')
+    expect(translate(ctx, 'product')).toEqual('Product')
+    expect(translate(ctx, 'product.type')).toEqual('Product type')
+    expect(translate(ctx, 'product.test.type')).toEqual('Product test type')
+  })
 })
 
 test('locale detector', () => {
diff --git a/packages/vue-i18n-core/src/utils.ts b/packages/vue-i18n-core/src/utils.ts
index 1b6b01922..da42038c0 100644
--- a/packages/vue-i18n-core/src/utils.ts
+++ b/packages/vue-i18n-core/src/utils.ts
@@ -1,4 +1,5 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
+import { AST_NODE_PROPS_KEYS, isMessageAST } from '@intlify/core-base'
 import {
   create,
   deepCopy,
@@ -48,6 +49,10 @@ export function handleFlatJson(obj: unknown): unknown {
     return obj
   }
 
+  if (isMessageAST(obj)) {
+    return obj
+  }
+
   for (const key in obj as object) {
     // check key
     if (!hasOwn(obj, key)) {
@@ -89,12 +94,26 @@ export function handleFlatJson(obj: unknown): unknown {
       }
       // update last object value, delete old property
       if (!hasStringValue) {
-        currentObj[subKeys[lastIndex]] = obj[key]
-        delete obj[key]
+        if (!isMessageAST(currentObj)) {
+          currentObj[subKeys[lastIndex]] = obj[key]
+          delete obj[key]
+        } else {
+          /**
+           * NOTE:
+           * if the last object is a message AST and subKeys[lastIndex] has message AST prop key, ignore to copy and key deletion
+           */
+          if (!AST_NODE_PROPS_KEYS.includes(subKeys[lastIndex])) {
+            delete obj[key]
+          }
+        }
       }
+
       // recursive process value if value is also a object
-      if (isObject(currentObj[subKeys[lastIndex]])) {
-        handleFlatJson(currentObj[subKeys[lastIndex]])
+      if (!isMessageAST(currentObj)) {
+        const target = currentObj[subKeys[lastIndex]]
+        if (isObject(target)) {
+          handleFlatJson(target)
+        }
       }
     }
   }
diff --git a/packages/vue-i18n-core/test/fixtures/ast.ts b/packages/vue-i18n-core/test/fixtures/ast.ts
new file mode 100644
index 000000000..e616e0134
--- /dev/null
+++ b/packages/vue-i18n-core/test/fixtures/ast.ts
@@ -0,0 +1,126 @@
+export const ast = {
+  language: {
+    type: 0,
+    start: 0,
+    end: 9,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 10, offset: 9 },
+      source: 'Languages'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 9,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 10, offset: 9 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 9,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 10, offset: 9 }
+          }
+        }
+      ],
+      static: 'Languages'
+    }
+  },
+  product: {
+    type: 0,
+    start: 0,
+    end: 7,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 8, offset: 7 },
+      source: 'Product'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 7,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 8, offset: 7 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 7,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 8, offset: 7 }
+          }
+        }
+      ],
+      static: 'Product'
+    }
+  },
+  'product.type': {
+    type: 0,
+    start: 0,
+    end: 12,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 13, offset: 12 },
+      source: 'Product type'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 12,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 13, offset: 12 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 12,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 13, offset: 12 }
+          }
+        }
+      ],
+      static: 'Product type'
+    }
+  },
+  'product.test.type': {
+    type: 0,
+    start: 0,
+    end: 17,
+    loc: {
+      start: { line: 1, column: 1, offset: 0 },
+      end: { line: 1, column: 18, offset: 17 },
+      source: 'Product test type'
+    },
+    body: {
+      type: 2,
+      start: 0,
+      end: 17,
+      loc: {
+        start: { line: 1, column: 1, offset: 0 },
+        end: { line: 1, column: 18, offset: 17 }
+      },
+      items: [
+        {
+          type: 3,
+          start: 0,
+          end: 17,
+          loc: {
+            start: { line: 1, column: 1, offset: 0 },
+            end: { line: 1, column: 18, offset: 17 }
+          }
+        }
+      ],
+      static: 'Product test type'
+    }
+  }
+}
diff --git a/packages/vue-i18n-core/test/issues.test.ts b/packages/vue-i18n-core/test/issues.test.ts
index d39ab3707..c659db879 100644
--- a/packages/vue-i18n-core/test/issues.test.ts
+++ b/packages/vue-i18n-core/test/issues.test.ts
@@ -23,6 +23,7 @@ import {
 } from '@intlify/core-base'
 import { defineComponent, getCurrentInstance, nextTick, ref } from 'vue'
 import { createI18n, useI18n } from '../src/i18n'
+import { ast } from './fixtures/ast'
 import { mount } from './helper'
 
 import type { ComponentOptions } from 'vue'
@@ -1181,3 +1182,30 @@ describe('CVE-2024-52809', () => {
     expect(() => i18n.global.t('hello')).toThrow(`unhandled node type: 3`)
   })
 })
+
+describe('#2156', () => {
+  test('flatJson: false', () => {
+    const i18n = createI18n({
+      locale: 'en',
+      messages: {
+        en: ast
+      }
+    })
+    expect(i18n.global.t('product')).toEqual('Product')
+    expect(i18n.global.t('product.type')).toEqual('Product type')
+    expect(i18n.global.t('product.test.type')).toEqual('Product test type')
+  })
+
+  test('flatJson: true', () => {
+    const i18n = createI18n({
+      locale: 'en',
+      flatJson: true,
+      messages: {
+        en: ast
+      }
+    })
+    expect(i18n.global.t('product')).toEqual('Product')
+    expect(i18n.global.t('product.type')).toEqual('Product type')
+    expect(i18n.global.t('product.test.type')).toEqual('Product test type')
+  })
+})
diff --git a/packages/vue-i18n-core/test/utils.test.ts b/packages/vue-i18n-core/test/utils.test.ts
index eb02db40e..52660e910 100644
--- a/packages/vue-i18n-core/test/utils.test.ts
+++ b/packages/vue-i18n-core/test/utils.test.ts
@@ -77,4 +77,9 @@ describe('handleFlatJson', () => {
     // @ts-ignore -- test
     expect(Object.prototype.pollutedKey).toBeUndefined()
   })
+
+  test('ast has json path', async () => {
+    const { ast } = await import('./fixtures/ast')
+    expect(handleFlatJson(ast)).toStrictEqual(ast)
+  })
 })