• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

expressjs / body-parser / 25132417397

29 Apr 2026 08:34PM UTC coverage: 98.268% (-0.8%) from 99.099%
25132417397

push

github

web-flow
perf: eliminate conditional check in json strict mode hot path (#651)

Refactor JSON parser creation to return specialized parser functions
based on strict mode setting, removing the per-request `if (strict)`
conditional check in the parsing hot path.

18 of 19 new or added lines in 1 file covered. (94.74%)

1 existing line in 1 file now uncovered.

227 of 231 relevant lines covered (98.27%)

4094.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.23
/lib/types/json.js
1
/*!
2
 * body-parser
3
 * Copyright(c) 2014 Jonathan Ong
4
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
5
 * MIT Licensed
6
 */
7

8
'use strict'
9

10
/**
11
 * Module dependencies.
12
 * @private
13
 */
14

15
var debug = require('debug')('body-parser:json')
9✔
16
var read = require('../read')
9✔
17
var { normalizeOptions } = require('../utils')
9✔
18

19
/**
20
 * Module exports.
21
 */
22

23
module.exports = json
9✔
24

25
/**
26
 * RegExp to match the first non-space in a string.
27
 *
28
 * Allowed whitespace is defined in RFC 7159:
29
 *
30
 *    ws = *(
31
 *            %x20 /              ; Space
32
 *            %x09 /              ; Horizontal tab
33
 *            %x0A /              ; Line feed or New line
34
 *            %x0D )              ; Carriage return
35
 */
36
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
9✔
37

38
var JSON_SYNTAX_CHAR = '#'
9✔
39
var JSON_SYNTAX_REGEXP = /#+/g
9✔
40

41
/**
42
 * Create a middleware to parse JSON bodies.
43
 *
44
 * @param {Object} [options]
45
 * @returns {Function}
46
 * @public
47
 */
48
function json (options) {
49
  const normalizedOptions = normalizeOptions(options, 'application/json')
333✔
50

51
  const parse = createJsonParser(options)
324✔
52

53
  const readOptions = {
324✔
54
    ...normalizedOptions,
55
    // assert charset per RFC 7159 sec 8.1
56
    isValidCharset: (charset) => charset.slice(0, 4) === 'utf-'
522✔
57
  }
58

59
  return function jsonParser (req, res, next) {
324✔
60
    read(req, res, next, parse, debug, readOptions)
585✔
61
  }
62
}
63

64
/**
65
 * Create a JSON parse function
66
 *
67
 * @param {object} [options]
68
 * @return {function}
69
 * @private
70
 */
71
function createJsonParser (options) {
72
  const reviver = options?.reviver
324✔
73
  const strict = options?.strict !== false
324✔
74

75
  if (strict) {
324✔
76
    return function parse (body) {
315✔
77
      if (body.length === 0) {
333✔
78
        // special-case empty json body, as it's a common client-side mistake
79
        // TODO: maybe make this configurable or part of "strict" option
80
        return {}
18✔
81
      }
82

83
      const first = firstchar(body)
315✔
84
      if (first !== '{' && first !== '[') {
315✔
85
        debug('strict violation')
54✔
86
        throw createStrictSyntaxError(body, first)
54✔
87
      }
88

89
      try {
261✔
90
        debug('parse json')
261✔
91
        return JSON.parse(body, reviver)
261✔
92
      } catch (e) {
93
        throw normalizeJsonSyntaxError(e, {
36✔
94
          message: e.message,
95
          stack: e.stack
96
        })
97
      }
98
    }
99
  }
100

101
  return function parse (body) {
9✔
102
    if (body.length === 0) {
9✔
103
      // special-case empty json body, as it's a common client-side mistake
104
      // TODO: maybe make this configurable or part of "strict" option
NEW
105
      return {}
×
106
    }
107

108
    try {
9✔
109
      debug('parse json')
9✔
110
      return JSON.parse(body, reviver)
9✔
111
    } catch (e) {
UNCOV
112
      throw normalizeJsonSyntaxError(e, {
×
113
        message: e.message,
114
        stack: e.stack
115
      })
116
    }
117
  }
118
}
119

120
/**
121
 * Create strict violation syntax error matching native error.
122
 *
123
 * @param {string} str
124
 * @param {string} char
125
 * @returns {Error}
126
 * @private
127
 */
128
function createStrictSyntaxError (str, char) {
129
  var index = str.indexOf(char)
54✔
130
  var partial = ''
54✔
131

132
  if (index !== -1) {
54✔
133
    partial = str.substring(0, index) + JSON_SYNTAX_CHAR.repeat(str.length - index)
36✔
134
  }
135

136
  try {
54✔
137
    JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
54✔
138
  } catch (e) {
139
    return normalizeJsonSyntaxError(e, {
54✔
140
      message: e.message.replace(JSON_SYNTAX_REGEXP, function (placeholder) {
141
        return str.substring(index, index + placeholder.length)
64✔
142
      }),
143
      stack: e.stack
144
    })
145
  }
146
}
147

148
/**
149
 * Get the first non-whitespace character in a string.
150
 *
151
 * @param {string} str
152
 * @returns {string|undefined}
153
 * @private
154
 */
155
function firstchar (str) {
156
  var match = FIRST_CHAR_REGEXP.exec(str)
315✔
157

158
  return match
315✔
159
    ? match[1]
160
    : undefined
161
}
162

163
/**
164
 * Normalize a SyntaxError for JSON.parse.
165
 *
166
 * @param {SyntaxError} error
167
 * @param {Object} obj
168
 * @returns {SyntaxError}
169
 * @private
170
 */
171
function normalizeJsonSyntaxError (error, obj) {
172
  var keys = Object.getOwnPropertyNames(error)
90✔
173

174
  for (var i = 0; i < keys.length; i++) {
90✔
175
    var key = keys[i]
180✔
176
    if (key !== 'stack' && key !== 'message') {
180✔
177
      delete error[key]
×
178
    }
179
  }
180

181
  // replace stack before message for Node.js 0.10 and below
182
  error.stack = obj.stack.replace(error.message, obj.message)
90✔
183
  error.message = obj.message
90✔
184

185
  return error
90✔
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc