mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
Bugfix/Parse JSON correctly (#5220)
* parse JSON correctly * add codeblock highlight
This commit is contained in:
@@ -8,8 +8,7 @@ import {
|
||||
IServerSideEventStreamer
|
||||
} from '../../../src/Interface'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { getCredentialData, getCredentialParam, processTemplateVariables } from '../../../src/utils'
|
||||
import JSON5 from 'json5'
|
||||
import { getCredentialData, getCredentialParam, processTemplateVariables, parseJsonBody } from '../../../src/utils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { BaseMessageLike } from '@langchain/core/messages'
|
||||
import { updateFlowState } from '../utils'
|
||||
@@ -168,7 +167,7 @@ class ExecuteFlow_Agentflow implements INode {
|
||||
let overrideConfig = nodeData.inputs?.executeFlowOverrideConfig
|
||||
if (typeof overrideConfig === 'string' && overrideConfig.startsWith('{') && overrideConfig.endsWith('}')) {
|
||||
try {
|
||||
overrideConfig = JSON5.parse(overrideConfig)
|
||||
overrideConfig = parseJsonBody(overrideConfig)
|
||||
} catch (parseError) {
|
||||
throw new Error(`Invalid JSON in executeFlowOverrideConfig: ${parseError.message}`)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter
|
||||
import { AxiosRequestConfig, Method, ResponseType } from 'axios'
|
||||
import FormData from 'form-data'
|
||||
import * as querystring from 'querystring'
|
||||
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { getCredentialData, getCredentialParam, parseJsonBody } from '../../../src/utils'
|
||||
import { secureAxiosRequest } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
class HTTP_Agentflow implements INode {
|
||||
label: string
|
||||
@@ -20,16 +19,6 @@ class HTTP_Agentflow implements INode {
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
private parseJsonBody(body: string): any {
|
||||
try {
|
||||
return JSON5.parse(body)
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Invalid JSON format in body. Original error: ${error.message}. Please ensure your JSON is properly formatted with quoted strings and valid escape sequences.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.label = 'HTTP'
|
||||
this.name = 'httpAgentflow'
|
||||
@@ -285,7 +274,7 @@ class HTTP_Agentflow implements INode {
|
||||
if (method !== 'GET' && body) {
|
||||
switch (bodyType) {
|
||||
case 'json': {
|
||||
requestConfig.data = typeof body === 'string' ? this.parseJsonBody(body) : body
|
||||
requestConfig.data = typeof body === 'string' ? parseJsonBody(body) : body
|
||||
requestHeaders['Content-Type'] = 'application/json'
|
||||
break
|
||||
}
|
||||
@@ -303,7 +292,7 @@ class HTTP_Agentflow implements INode {
|
||||
break
|
||||
}
|
||||
case 'xWwwFormUrlencoded':
|
||||
requestConfig.data = querystring.stringify(typeof body === 'string' ? this.parseJsonBody(body) : body)
|
||||
requestConfig.data = querystring.stringify(typeof body === 'string' ? parseJsonBody(body) : body)
|
||||
requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
class Iteration_Agentflow implements INode {
|
||||
label: string
|
||||
@@ -42,10 +42,10 @@ class Iteration_Agentflow implements INode {
|
||||
// Helper function to clean JSON strings with redundant backslashes
|
||||
const safeParseJson = (str: string): string => {
|
||||
try {
|
||||
return JSON5.parse(str)
|
||||
return parseJsonBody(str)
|
||||
} catch {
|
||||
// Try parsing after cleaning
|
||||
return JSON5.parse(str.replace(/\\(["'[\]{}])/g, '$1'))
|
||||
return parseJsonBody(str.replace(/\\(["'[\]{}])/g, '$1'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Tool } from '@langchain/core/tools'
|
||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
|
||||
import { MCPToolkit, validateMCPServerConfig } from '../core'
|
||||
import { getVars, prepareSandboxVars } from '../../../../src/utils'
|
||||
import { getVars, prepareSandboxVars, parseJsonBody } from '../../../../src/utils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import hash from 'object-hash'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const mcpServerConfig = `{
|
||||
"command": "npx",
|
||||
@@ -270,7 +269,7 @@ function substituteVariablesInString(str: string, sandbox: any): string {
|
||||
|
||||
function convertToValidJSONString(inputString: string) {
|
||||
try {
|
||||
const jsObject = JSON5.parse(inputString)
|
||||
const jsObject = parseJsonBody(inputString)
|
||||
return JSON.stringify(jsObject, null, 2)
|
||||
} catch (error) {
|
||||
console.error('Error converting to JSON:', error)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { desc, RequestParameters, RequestsDeleteTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"id": {
|
||||
@@ -131,7 +130,7 @@ class RequestsDelete_Tools implements INode {
|
||||
if (queryParamsSchema) obj.queryParamsSchema = queryParamsSchema
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you need to execute a DELETE request to remove data from a website.`
|
||||
|
||||
@@ -23,7 +23,7 @@ const createRequestsDeleteSchema = (queryParamsSchema?: string) => {
|
||||
// If queryParamsSchema is provided, parse it and add dynamic query params
|
||||
if (queryParamsSchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(queryParamsSchema)
|
||||
const queryParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
@@ -109,7 +109,7 @@ export class RequestsDeleteTool extends DynamicStructuredTool {
|
||||
|
||||
if (this.queryParamsSchema && params.queryParams && Object.keys(params.queryParams).length > 0) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(this.queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(this.queryParamsSchema)
|
||||
const pathParams: Array<{ key: string; value: string }> = []
|
||||
|
||||
Object.entries(params.queryParams).forEach(([key, value]) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { desc, RequestParameters, RequestsGetTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"id": {
|
||||
@@ -131,7 +130,7 @@ class RequestsGet_Tools implements INode {
|
||||
if (queryParamsSchema) obj.queryParamsSchema = queryParamsSchema
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you need to execute a GET request to get data from a website.`
|
||||
|
||||
@@ -23,7 +23,7 @@ const createRequestsGetSchema = (queryParamsSchema?: string) => {
|
||||
// If queryParamsSchema is provided, parse it and add dynamic query params
|
||||
if (queryParamsSchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(queryParamsSchema)
|
||||
const queryParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
@@ -109,7 +109,7 @@ export class RequestsGetTool extends DynamicStructuredTool {
|
||||
|
||||
if (this.queryParamsSchema && params.queryParams && Object.keys(params.queryParams).length > 0) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(this.queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(this.queryParamsSchema)
|
||||
const pathParams: Array<{ key: string; value: string }> = []
|
||||
|
||||
Object.entries(params.queryParams).forEach(([key, value]) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { RequestParameters, desc, RequestsPostTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"name": {
|
||||
@@ -141,11 +140,11 @@ class RequestsPost_Tools implements INode {
|
||||
if (bodySchema) obj.bodySchema = stripHTMLFromToolInput(bodySchema)
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
if (body) {
|
||||
const parsedBody = typeof body === 'object' ? body : JSON5.parse(body)
|
||||
const parsedBody = typeof body === 'object' ? body : parseJsonBody(body)
|
||||
obj.body = parsedBody
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you want to execute a POST request to create or update a resource.`
|
||||
|
||||
@@ -28,7 +28,7 @@ const createRequestsPostSchema = (bodySchema?: string) => {
|
||||
// If bodySchema is provided, parse it and add dynamic body params
|
||||
if (bodySchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(bodySchema)
|
||||
const parsedSchema = parseJsonBody(bodySchema)
|
||||
const bodyParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { RequestParameters, desc, RequestsPutTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"name": {
|
||||
@@ -141,11 +140,11 @@ class RequestsPut_Tools implements INode {
|
||||
if (bodySchema) obj.bodySchema = stripHTMLFromToolInput(bodySchema)
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
if (body) {
|
||||
const parsedBody = typeof body === 'object' ? body : JSON5.parse(body)
|
||||
const parsedBody = typeof body === 'object' ? body : parseJsonBody(body)
|
||||
obj.body = parsedBody
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you want to execute a PUT request to update or replace a resource.`
|
||||
|
||||
@@ -28,7 +28,7 @@ const createRequestsPutSchema = (bodySchema?: string) => {
|
||||
// If bodySchema is provided, parse it and add dynamic body params
|
||||
if (bodySchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(bodySchema)
|
||||
const parsedSchema = parseJsonBody(bodySchema)
|
||||
const bodyParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
|
||||
@@ -1408,7 +1408,7 @@ const parseOutput = (output: any): any => {
|
||||
// Check if it looks like JSON (starts with { or [)
|
||||
if ((trimmedOutput.startsWith('{') && trimmedOutput.endsWith('}')) || (trimmedOutput.startsWith('[') && trimmedOutput.endsWith(']'))) {
|
||||
try {
|
||||
const parsedOutput = JSON5.parse(trimmedOutput)
|
||||
const parsedOutput = parseJsonBody(trimmedOutput)
|
||||
return parsedOutput
|
||||
} catch (e) {
|
||||
return output
|
||||
@@ -1659,3 +1659,71 @@ export const processTemplateVariables = (state: ICommonObject, finalOutput: any)
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON body with comprehensive error handling and cleanup
|
||||
* @param {string} body - The JSON string to parse
|
||||
* @returns {any} - The parsed JSON object
|
||||
* @throws {Error} - Detailed error message with suggestions for common JSON issues
|
||||
*/
|
||||
export const parseJsonBody = (body: string): any => {
|
||||
try {
|
||||
// First try to parse as-is with JSON5 (which handles more cases than standard JSON)
|
||||
return JSON5.parse(body)
|
||||
} catch (error) {
|
||||
try {
|
||||
// If that fails, try to clean up common issues
|
||||
let cleanedBody = body
|
||||
|
||||
// 1. Remove unnecessary backslash escapes for square brackets and braces
|
||||
// eslint-disable-next-line
|
||||
cleanedBody = cleanedBody.replace(/\\(?=[\[\]{}])/g, '')
|
||||
|
||||
// 2. Fix single quotes to double quotes (but preserve quotes inside strings)
|
||||
cleanedBody = cleanedBody.replace(/'/g, '"')
|
||||
|
||||
// 3. Remove trailing commas before closing brackets/braces
|
||||
cleanedBody = cleanedBody.replace(/,(\s*[}\]])/g, '$1')
|
||||
|
||||
// 4. Remove comments (// and /* */)
|
||||
cleanedBody = cleanedBody
|
||||
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
||||
|
||||
return JSON5.parse(cleanedBody)
|
||||
} catch (secondError) {
|
||||
try {
|
||||
// 3rd attempt: try with standard JSON.parse on original body
|
||||
return JSON.parse(body)
|
||||
} catch (thirdError) {
|
||||
try {
|
||||
// 4th attempt: try with standard JSON.parse on cleaned body
|
||||
const finalCleanedBody = body
|
||||
// eslint-disable-next-line
|
||||
.replace(/\\(?=[\[\]{}])/g, '') // Basic escape cleanup
|
||||
.replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas
|
||||
.trim()
|
||||
|
||||
return JSON.parse(finalCleanedBody)
|
||||
} catch (fourthError) {
|
||||
// Provide comprehensive error message with suggestions
|
||||
const suggestions = [
|
||||
'• Ensure all strings are enclosed in double quotes',
|
||||
'• Remove trailing commas',
|
||||
'• Remove comments (// or /* */)',
|
||||
'• Escape special characters properly (\\n for newlines, \\" for quotes)',
|
||||
'• Use double quotes instead of single quotes',
|
||||
'• Remove unnecessary backslashes before brackets [ ] { }'
|
||||
]
|
||||
|
||||
throw new Error(
|
||||
`Invalid JSON format in body. Original error: ${error.message}. ` +
|
||||
`After cleanup attempts: ${secondError.message}. 3rd attempt: ${thirdError.message}. Final attempt: ${fourthError.message}.\n\n` +
|
||||
`Common fixes:\n${suggestions.join('\n')}\n\n` +
|
||||
`Received body: ${body.substring(0, 200)}${body.length > 200 ? '...' : ''}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user