-1

I have String like

"[{'techid':'0128','daPoints':3,'speedingPoints':3,'fleetInspectionPoints':3,'lofPoints':3,'missedTrgModules':null,'fullName':'MANPREET SINGH','safetyInspectPoints':3,'missedTrgPoints':3,'speeding_qty':null,'safetyTotalPoints':21,'atFaultPoints':3,'atFaultAccident':null,'region':'PYEM','supervisor':'AGHATOR OSA','driverAlert':null,'status':'A'}]"

need to convert into Json format trying something like this

const text = "[{'techid':'0128','daPoints':3,'speedingPoints':3,'fleetInspectionPoints':3,'lofPoints':3,'missedTrgModules':null,'fullName':'MANPREET SINGH','safetyInspectPoints':3,'missedTrgPoints':3,'speeding_qty':null,'safetyTotalPoints':21,'atFaultPoints':3,'atFaultAccident':null,'region':'PYEM','supervisor':'AGHATOR OSA','driverAlert':null,'status':'A'}]";
const myArr = JSON.parse(text);
document.getElementById("demo").innerHTML = myArr[0];

But getting error :-

Uncaught SyntaxError: Expected property name or '}' in JSON at position 2
11
  • 1
    I believe json spec states you need double quotes for identifiers, not single quotes. Commented Jan 11, 2023 at 19:00
  • right but how we can replace Commented Jan 11, 2023 at 19:01
  • 1
    "[{\"techid\":\"0128\",\"daPoints\":3,\"speedingPoints\":3,\"fleetInspectionPoints\":3,\"lofPoints\":3,\"missedTrgModules\":null,\"fullName\":\"MANPREET SINGH\",\"safetyInspectPoints\":3,\"missedTrgPoints\":3,\"speeding_qty\":null,\"safetyTotalPoints\":21,\"atFaultPoints\":3,\"atFaultAccident\":null,\"region\":\"PYEM\",\"supervisor\":\"AGHATOR OSA\",\"driverAlert\":null,\"status\":\"A\"}]" Commented Jan 11, 2023 at 19:01
  • @Igor LOL !! do we have any way by using code Commented Jan 11, 2023 at 19:06
  • 2
    The marked answer works until there is a record with a string value that contains an ' as part of the value. Example: 'fullName': 'Danny O'Reilly' which then becomes "fullName": "Danny O"Reilly" which now is not only invalid but also wrong as it should be converted to "fullName": "Danny O'Reilly". Again, the source that produces the json needs to be fixed or you need to invest some time into creating a full proof parsing strategy that can handle edge cases. Commented Jan 11, 2023 at 21:28

2 Answers 2

1

Simple Solution:

Convert all of the single-quoted strings and properties (that are invalid JSON in the specification) to valid JSON with double-quotes using a Regular Expression (RegExp):

const unformattedText = "[{'techid':'0128','daPoints':3,'speedingPoints':3,'fleetInspectionPoints':3,'lofPoints':3,'missedTrgModules':null,'fullName':'MANPREET SINGH','safetyInspectPoints':3,'missedTrgPoints':3,'speeding_qty':null,'safetyTotalPoints':21,'atFaultPoints':3,'atFaultAccident':null,'region':'PYEM','supervisor':'AGHATOR OSA','driverAlert':null,'status':'A'}]"

// convert with a regex
const formattedText = unformattedText.replace(/'/g, "\"")
console.log(formattedText)

// convert to object
const object = JSON.parse(formattedText)
console.log(object)

NOTE: As @Igor pointed out, this will only work for JSON strings that don't include properties or strings with single-quotes ', (like the OP's).

Update:

My String.prototype.replace() solution behavior:

Cannot recover any field or string with a single-quote ' in it,

Example:

     [{'name':'Bob O'Rielly','height':'13',5"'}]

Becomes:

     [{"name":"Bob O"Rielly","height":"13",5""}]

which is incorrect and invalid JSON!

Over-Engineered Solution: Literally an entire parser...

It should be well commented enough to be able to use and modify to anyone's needs. GitHub repo (Permalink)

WARNING: In brute-force mode can be O(2^n)! Which is very inefficient.

The presence of incorrect string delimiters causes ambiguity during parsing. And useful solutions are very computationally expensive.

/**
 * @typedef  {function(StringPosition): boolean} BruteForce
 *                         A function that takes information about the current
 *                         position of the single-quote in the misformatted
 *                         string the parser found.
 *
 *                         And returns a boolean whether the parser will use
 *                         brute-force parsing, with time-complexity `O(2^n)`.
 *
 * @typedef  {function(StringPosition): Guess} Heuristic
 *                         A function that takes a reference the parser state
 *                         information, and modifies the parser guess state
 *                         using the given reference.
 *
 *                         This allows for much more complex logic.
 *
 *                         Example:
 *
 *                         Situations where the single-quote (`'`) needs to be
 *                         replaced by an escaped double-quote (`\\"`).
 *
 * @typedef  {("array"|"field"|"object-value"|"value")} JSONType
 *                         A string union type JSON types for arrays, object
 *                         fields, object values, and values.
 *
 * @typedef  {("\""|"\'")} Quote
 *                         One single-quote (`'`) or double-quote (`""`)
 *                         string character.
 *
 * @typedef  {("\\\""|"\\'")} EscapedQuote
 *                         One single-quote (`\\'`) or double-quote (`\\"`)
 *                         string character, preceded by a reverse solidus
 *                         (`\`), to escape it from the usual JSON
 *                         interpretation.
 *
 * @typedef  {Object}      StringPosition
 *                         An object containing information on where in a
 *                         string a single-quote character was found by the
 *                         parser.
 *
 * @property {Number}      StringPosition.stringIndex The index of the
 *                         single-quote in the misformatted string the parser
 *                         is currently trying to correct.
 *
 * @property {Number}      StringPosition.characterIndex The UTF-32 character
 *                         pseudo-index `index - (UFT-16 surrogate pairs /2)`
 *                         of the single-quote in the misformatted string the
 *                         parser is currently trying to correct.
 *
 * @property {String}      StringPosition.followingString The JSON substring
 *                         after the single-quote.
 *
 * @property {String}      StringPosition.precedingString The JSON substring
 *                         before the single-quote.
 *
 * @property {Guess}       StringPosition.currentGuess An object containing
 *                         information about a parser attempt to reformat a
 *                         JSON string. It contains a reformatted substring
 *                         the parser generated, and whether the internal
 *                         heuristics consider the substring to end in any
 *                         incompleted JSON type.
 *
 * @typedef  {Object}      Guess
 *                         An object containing information about a parser
 *                         attempt to reformat a JSON string. It contains a
 *                         reformatted substring the parser generated, and
 *                         whether the internal heuristics should consider the
 *                         substring to end in an incompleted JSON string.
 *
 * @property {String}      Guess.string The parser guess of how to reformat
 *                         JSON string. It is set to the previously parsed
 *                         guess, with future parser guesses appended to it.
 *
 * @property {Boolean}     Guess.isString The internal flag for `preParse`
 *                         that determines whether the algorithm interprets
 *                         current parsing as parsing the middle of a JSON
 *                         string value.
 *
 * @property {Boolean}     Guess.isEscaped The internal flag for `preParse`
 *                         that determines whether the algorithm interprets
 *                         current parsing as parsing a character escape
 *                         sequence in the middle of a JSON string value. It is
 *                         a number to accommodate unicode escape sequences.
 *
 *                         Only works for:  `\'`. not: `\"`, `\\`, `\/`, `\n`,
 *                         `\r`, `\b`, `\f`, `\t` or `\uXXXX`.
 *
 * @property {Boolean}     Guess.isObjectField The internal flag for
 *                         `preParse` that determines whether the algorithm
 *                         interprets the current parsing state as parsing
 *                         the middle of a JSON object value.
 *
 * @property {Boolean}     Guess.isObjectValue The internal flag for
 *                         `preParse` that determines whether the algorithm
 *                         interprets the current parsing state as parsing
 *                         the middle of a JSON object field.
 *
 * @property {Number}      Guess.isObject The internal flag for `preParse`
 *                         that determines whether the algorithm interprets
 *                         the current parsing state as parsing the middle of
 *                         a JSON object. It is a number to track how many
 *                         objects the single-quote is nested within.
 *
 * @property {Number}      Guess.isArray The internal flag for `preParse` that
 *                         determines whether the algorithm interprets the
 *                         current parsing state as parsing the middle of a
 *                         JSON array value. It is a number to track how many
 *                         arrays the single-quote is nested within.
 *
 * @property {Number}      Guess.aufoFilled The internal flag for `preParse`
 *                         that determines whether the algorithm already
 *                         reformatted this string index with a previous RegEx
 *                         heuristic solution. Is a string for how many future
 *                         indexes were prefilled. Default: `0`.
 *
 * @property {JSONType}    [Guess.isJSONType] The internal flag for `preParse`
 *                         that determines whether the algorithm interprets
 *                         the current parsing state as parsing the middle of
 *                         specific JSON type. It is a string union type for
 *                         arrays, object fields, object values, and values.
 *                         Used to keep track of the JSON type being parsed,
 *                         without including type nesting depth information.
 */

/**
 * @description            Reformats a misformatted JSON string to valid JSON.
 *
 *                         **NOTE:** This function assumes that all
 *                         double-quotes are escaped correctly, like: `\"`.
 *
 *                         If there are improperly escaped double-quotes, this
 *                         function will not be able to reformat the
 *                         misformatted JSON properly, unless custom logic in
 *                         an `opts.heuristic` function is used.
 *
 *                         **NOTE:** this is _**NOT**_ designed for
 *                         performance or large JSON strings. This can have
 *                         `O(2^n)` time-complexity!
 *
 * @param    {String}      misformattedJSON The string that is invalidly
 *                         formatted for JSON by using single-quotes (`'`)
 *                         instead of double-qutoes (`"`) to delimit string
 *                         values and object fields.
 *
 * @param    {Heuristic}   [heuristic] A function that takes a reference the
 *                         parser state information, and modifies the parser
 *                         guess state using the given reference. This allows
 *                         for much more complex logic.
 *
 *                         Example:
 *
 *                         Situations where the single-quote (`'`) needs to be
 *                         replaced by an escaped double-quote (`\\"`).
 *
 * @param    {BruteForce}  [bruteForce] A function that takes information
 *                         about the current position of the single-quote in
 *                         the misformatted string the parser found. And
 *                         returns a boolean whether the parser will use
 *                         brute-force parsing, with time-complexity `O(2^n)`.
 *
 * @param    {Boolean}     [TRY_HARD] A flag to brute-force with default,
 *                         heuristics, with time-complexity at around:
 *                         `O(2^n)`. Can be better than a `bruteForce`, but
 *                         still not reccommended, use `heuristic` if possible.
 *
 * @returns  {String[]}    The reformatted string, or array of reformatted
 *                         strings, if there was no error in parsing.
 */
function preParse(misformattedJSON, bruteForce, heuristic, TRY_HARD) {
    /*
  if (TRY_HARD)
    console.warn(
      "THIS HAS O(2^n) TIME COMPLEXITY, MAKE SURE YOU KNOW WHAT YOU'RE DOING..."
    )
  */

    /**
     * @type   {Guess[]}     Array to store all of the parser's possible
     *                       solutions. Holds strings of the parser guesses.
     *                       Used when strings are ambiguous because of
     *                       incorrect formatting, and brute-forcing. Is
     *                       appended to by recursive calls to `preParse`.
     */
    let guesses = [
        {
            string: "",
            isString: false,
            isEscaped: false,
            isArray: 0,
            isObject: 0,
            isObjectField: false,
            isObjectValue: false,
            isJSONType: undefined,
            aufoFilled: 0
        }
    ]

    /**
     * @type   {String[]}    Fixed-width array to index each UTF-32 character.
     *                       JavaScript strings are indexed by UTF-16 code units,
     *                       which are 2 bytes (16 bits) in length, a single
     *                       UTF-32 character is 4 bytes (32 bits), the same as
     *                       a complete UTF-16 surrogate pair, (which is two
     *                       UTF-16 code units).
     */
    let fixedWidthCharacterArray = []

    // iteration over each UTF-32 code point to add to UTF-32 array
    for (const UTF16CodePoint of misformattedJSON)
        fixedWidthCharacterArray.push(UTF16CodePoint)

    // loop over UTF-32 characters
    for (
        let characterIndex = 0, stringIndex = 0;
        characterIndex < fixedWidthCharacterArray.length;
        stringIndex += fixedWidthCharacterArray[characterIndex].length,
            characterIndex++
    ) {
        /**
         * @type {String}      Current UTF-32 code point in character array.
         */
        const character = fixedWidthCharacterArray[characterIndex]

        // loop over all guesses, and evaluate thier likelihood,
        // reversing over array to add or remove guesses
        // without changing the portion of the array being looped over
        for (let guessIndex = guesses.length - 1; guessIndex >= 0; guessIndex--) {
            /**
             * @type {Guess}     Parsers's guess of how to reformat JSON string. It
             *                   is set to the previous parser guess, with future
             *                   parser guesses appended to it. It also includes
             *                   state information used for the default heuristics.
             */
            const currentGuess = guesses[guessIndex]

            // check guess auto-fill
            if (currentGuess.aufoFilled) {
                // decrement number of pre-parsed characters,
                // since one was just manually parsed
                currentGuess.aufoFilled--

                // guess already parsed this part of the string using a RegExp,
                // loop to next guess
                continue
            }

            const {
                isArray,
                isEscaped,
                isObject,
                isObjectField,
                isObjectValue,
                isString,
                isJSONType
            } = currentGuess

            /**
             * @type {String}    The substring _after_ the single-quote.
             */
            const followingString = misformattedJSON.slice(stringIndex + 1)

            /**
             * @type {String}    The substring _before_ the single-quote.
             */
            const precedingString = misformattedJSON.slice(0, stringIndex)

            // change parser state for delimiters
            if (character !== "'") {
                // keep character
                currentGuess.string += character

                // set escape sequence, only for inside of a JSON string
                if (isString) {
                    // escape character found,
                    // if another escape character preceded this one,
                    // the following character is not part of an escape sequence
                    // otherwise, the following character is part of an escape sequence
                    if (character === "\\") currentGuess.isEscaped = !isEscaped

                    // if isEscaped is false, loop to possible end of string
                    // if isEscaped is true, ignore escaped single-quote character later
                    // ignore delimiter characters inside of a JSON string,
                    // since they do not overlap with escape sequence
                    continue
                }

                // set if in an object field and increase object nesting
                if (character === "{") {
                    // set if in an object field
                    currentGuess.isObjectField = true
                    currentGuess.isJSONType = "field"

                    // increase object nesting
                    currentGuess.isObject++
                }
                // unset in object field on delimiter and set in object value
                else if (character === ":") {
                    // unset object field on delimiter
                    currentGuess.isObjectField = false
                    currentGuess.isJSONType = "object-value"

                    // set in object value
                    currentGuess.isObjectValue = true
                }
                // unset in object value on delimiter and decrease object nesting
                else if (character === "}") {
                    // unset in object value on delimiter
                    currentGuess.isObjectValue = false

                    // decrease object nesting
                    currentGuess.isObject--

                    currentGuess.isJSONType =
                        // if this is nested in a root object,
                        !isArray && isObject
                            ? "object-value"
                            : // if this is nested in a root array,
                            !isObject && isArray
                            ? "array"
                            : // default JSON value type
                            isArray && isObject
                            ? "value"
                            : // end of JSON
                              undefined
                }
                // increase array nesting
                else if (character === "[") {
                    currentGuess.isArray++
                    currentGuess.isJSONType = "array"
                }
                // decrease array nesting
                else if (character === "]") {
                    currentGuess.isArray--

                    currentGuess.isJSONType =
                        // if this is nested in a root array,
                        !isObject && isArray
                            ? "array"
                            : // if this is nested in a root object,
                            !isArray && isObject
                            ? "object-value"
                            : // default JSON value type
                            isArray && isObject
                            ? "value"
                            : // end of JSON
                              undefined
                }
                // only for objects, not array in object, reset field and unset value
                else if (
                    isObjectValue &&
                    isJSONType === "object-value" &&
                    character === ","
                ) {
                    // last value ended in last object entry
                    currentGuess.isObjectValue = false

                    // new field started in next object entry
                    currentGuess.isObjectField = true
                    currentGuess.isJSONType = "field"
                }

                // only attempt to reformat single-quotes
                continue
            }

            // try all possible solutions by generating
            // all possible solutions by brute-force
            if (
                bruteForce?.({
                    // make copy to prevent mutations
                    currentGuess: { ...currentGuess },
                    followingString,
                    precedingString,
                    stringIndex,
                    characterIndex
                })
            ) {
                // create a new guess
                guesses.push({
                    ...currentGuess,
                    // try to make a valid JSON string
                    // by keeping this single-quote,
                    string: currentGuess.string + character

                    // // possibly change these to allow for different heuristics
                    // isString: currentGuess.isString,
                    // isEscaped: currentGuess.isEscaped,
                    // isArray: currentGuess.isArray,
                    // isObject: currentGuess.isObject
                    // isObjectField: currentGuess.isObjectField,
                    // isObjectValue: currentGuess.isObjectValue,
                })

                // try to make a valid JSON string
                // by replacing this single-quote with a double-quote
                //! must come after new guess creation
                currentGuess.string += '"'

                // // possibly change these to allow for different heuristics
                // currentGuess.isString = currentGuess.isString
                // currentGuess.isEscaped = currentGuess.isEscaped
                // currentGuess.isArray = currentGuess.isArray
                // currentGuess.isObject = currentGuess.isObject
                // currentGuess.isObjectField = currentGuess.isObjectField
                // currentGuess.isObjectValue = currentGuess.isObjectValue
            }
            // use user given heuristics for for how to replace single-quote,
            // or change guess parsing state
            else if (
                heuristic?.({
                    // give current parser guess and state info to heuristic
                    currentGuess,
                    followingString,
                    precedingString,
                    stringIndex,
                    characterIndex
                })
            ) {
                /* do nothing... `heuristic` modified object reference directly */
            }
            // use default heuristics
            else if (isString) {
                // skip if quote is escaped and clearly inside of a string
                if (isEscaped) {
                    // escape sequence finished
                    currentGuess.isEscaped = false

                    // add normal quote character back to string
                    currentGuess.string += character

                    // loop to next character
                    continue
                }

                /**
                 * @type {String}   JSON object, value, or array ending delimiters.
                 */
                let possibleDelimiters = ""

                // add contextual delimiters for use in heuristics

                // delimiter for ending an array,
                if (isJSONType === "array") possibleDelimiters = ",\\]"
                // delimiter for ending an object,
                else if (isJSONType === "object-value") possibleDelimiters = ",}"
                // delimiter for ending an object field
                else if (isJSONType === "field" || isObjectField)
                    possibleDelimiters = ":"
                // delimiter for ending a value
                else if (isJSONType === "value") possibleDelimiters = ",}\\]"

                /**
                 * @type {Boolean} This single-quote is followed by a JSON type
                 *                 delimiter, and likely should be a double-quote.
                 */
                const heuristicDelimiterFollowing =
                    !!possibleDelimiters &&
                    new RegExp(String.raw`^\s*[${possibleDelimiters}]`).test(
                        followingString
                    )

                /**
                 * @type {RegExpExecArray|null} Finds a different possible solution.
                 *                 Starting from the current character, and not the
                 *                 `followingString`, so the RegExp includes it in the
                 *                 results array, leaving the capturing group with just
                 *                 the alternate possible and validated solution.
                 */
                const heuristicFindValids = new RegExp(
                    String.raw`([^']*)'\s*[${possibleDelimiters}]`
                ).exec(misformattedJSON.slice(stringIndex))

                /**
                 * @type {(String|"")} The RegExp validated string capturing group of
                 *                 just the characters before the single-quote to
                 *                 replace in the RexExp auto-filled guess. Skip if
                 *                 empty string, or capturing group is empty string.
                 */
                const regexpCapturingGroup = heuristicFindValids
                    ? heuristicFindValids[1]
                    : ""

                // both this single-quote and a following single-quote
                // have potentially correct resolutions, making the solution ambiguous,
                // so create a new guess to try both solutions
                if (regexpCapturingGroup) {
                    // only the following single-quote RegExp heuristic passed,
                    // which likely means this is a string character that should be kept,
                    // so use RegEpx auto-fill solution

                    // this guess will replace the single-quote and end the string
                    guesses.push({
                        ...currentGuess,
                        isString: false,
                        string: currentGuess.string + '"'
                    })

                    if (TRY_HARD)
                        guesses.push({
                            ...currentGuess,
                            isString: true, // stay string
                            string: currentGuess.string + character
                        })

                    // keep single-quote and use RegExp solution of all characters before
                    // single-quote from RegExp search that needs to be replaced
                    currentGuess.string += character + regexpCapturingGroup + '"'

                    // the length of the entire RegExp guess string
                    // including ending double-quote
                    currentGuess.aufoFilled = regexpCapturingGroup.length + 1

                    // ended string with double-quote
                    currentGuess.isString = false
                }

                // only the current single-quote RegExp heuristic passed,
                // which likely means this is a misformatted string delimiter
                else if (heuristicDelimiterFollowing) {
                    // WORSE time-complexity mode, custom heuristics but with brute-force
                    if (TRY_HARD)
                        guesses.push({
                            ...currentGuess,
                            isString: false,
                            string: currentGuess.string + character
                        })

                    // replace current misformatted single-quote with a double-quote
                    currentGuess.string += '"'

                    // ended string with double-quote
                    currentGuess.isString = false
                }

                // neither RegExp heuristic passed,
                // which likely means this is a string character that should be kept
                else {
                    // WORSE time-complexity mode, custom heuristics but with brute-force
                    if (TRY_HARD)
                        guesses.push({
                            ...currentGuess,
                            isString: false, // end string
                            string: currentGuess.string + character
                        })

                    // keep single-quote
                    currentGuess.string += character
                }
            }
            // beginning of a JSON string, reformat single-quote
            else {
                // replace single-quote with double-quote
                currentGuess.string += '"'

                // parser now looking for ending single-quote that needs reformatting
                currentGuess.isString = true
            }
        }
    }

    // try all parsing guesses to see if one works
    /**
     * @type   {Number}      Index of guess for debugging information.
     */
    let guessIndex = 0

    /**
     * @type   {String[]}    Array of every valid JSON string reformatted.
     */
    const successes = []

    for (const guess of guesses) {
        try {
            JSON.parse(guess.string)

            // reformatting guess parsed as valid JSON!
            successes.push(guess.string)

            console.log(`successfully parsed #${guessIndex}:`, guess, successes)
        } catch (error) {
            // // catch JSON parsing error and output for debug
            // console.error(`failed: guess #${guessIndex}:`, guess)
        }

        guessIndex++
    }

    if (successes.length === 1)
        console.log("Parser found definitive answer!\nParsed:", successes[0])
    else if (successes.length > 1)
        console.log(
            "Parser found no definitive answer! Parser found ambiguous answers! Try setting `opts.heuristic` or `opts.bruteForce`, they can get better parsing!\nParsed:",
            successes
        )
    else
        console.error(
            "Parser Failed to parse any reformatting guess as a valid JSON string! Try setting `heuristic`, `TRY_HARD`, or `opts.bruteForce` (if necessary), they can get better parsing!"
        )

    // return all valid JSON string guesses
    return successes
}

const json1 = `['Bob O'Rielly']`
const json2 = `['Mr. O'McDonald, height 13',1\\"']`
const json3 = `[{'fullName':'Bob O'Rielly','height':'13',5\\"'}]`
const json4 = `[[''''''''''''],[[''''''''],'[]','{}',',',':'],'''''''''''''']`
const json5 = `[{'techid':'0128','daPoints':3,'speedingPoints':3,'fleetInspectionPoints':3,'lofPoints':3,'missedTrgModules':null,'fullName':'MANPREET SINGH','safetyInspectPoints':3,'missedTrgPoints':3,'speeding_qty':null,'safetyTotalPoints':21,'atFaultPoints':3,'atFaultAccident':null,'region':'PYEM','supervisor':'AGHATOR OSA','driverAlert':null,'status':'A'}]`
const json6 = `[{'fullName':'Rob O'Rielly','height':'70.5\\"'}]`
const json7 = `[{'fullName':'Dob MacRielly','height':'13',5\\"'}]`

console.log(`ANSWERS 1:`, preParse(json1))
console.log(`ANSWERS 2:`, preParse(json2, undefined, undefined, true))
console.log(`ANSWERS 3:`, preParse(json3, undefined, undefined, true))
console.log(`ANSWER 4:`, preParse(json4))
console.log(`ANSWER 5:`, preParse(json5))
console.log(
    `ANSWER 6:`,
    preParse(json6, () => true, undefined)
)
console.log(`ANSWER 7:`, preParse(json7, undefined, undefined, true))

Sign up to request clarification or add additional context in comments.

5 Comments

That works until there is a record with a string value that contains an ' as part of the value. Example: 'fullName': 'Danny O'Reilly' which then becomes "fullName": "Danny O"Reilly" which now is not only invalid but also wrong.
@Igor Very true. This only works for a string that doesn't include single quotes. However, if a more robust implementation is needed, it would essentially equate to writing a parser. Which would require far more complicated logic and proper error handling, which may be slightly out-of-scope and (over-engineered) for the simplicity of the OP's specific problem. Assuming only a few small JSON strings were incorrectly formatted. The simplest solution would be to reformat the majority of the string with the regex, and then manually edit the rest of the JSON for the specific edge cases.
.replace(/'/g, "\"") ... is not even in need of such a simple regex since it could be (re)written like ... .replaceAll("'", '"')
@Igor. I have updated my answer to cover all possible edge-cases...
@PeterSeliger. The browser support for replaceAll() is too low for me to be able to recommend it in general. Also, I have updated my answer to cover all possible edge-cases...
0

The OP is in need of a sanitizing replace task where the used regex needs to explicitly target the single quotes around any property name and any string value. Thus such a regex has to utilize both a positive lookbehind and a positive lookahead. It would look as follows ...

/(?<=[,{:])'|'(?=[:,}])/g

... and its functioning is described at its playground page.

const invalidlyQuotedJSON =
  "[{'techid':'0128','daPoints':3,'speedingPoints':3,'fleetInspectionPoints':3,'lofPoints':3,'missedTrgModules':null,'fullName':'Danny O\'Reilly','safetyInspectPoints':3,'missedTrgPoints':3,'speeding_qty':null,'safetyTotalPoints':21,'atFaultPoints':3,'atFaultAccident':null,'region':'PYEM','supervisor':'AGHATOR OSA','driverAlert':null,'status':'A'}]";

// see ... [https://regex101.com/r/gzCPaS/1]
const regXSingleQuotes = /(?<=[,{:])'|'(?=[:,}])/g;

const validJSON = invalidlyQuotedJSON.replace(regXSingleQuotes, '"');
const parsedData = JSON.parse(validJSON);

console.log({
  invalidlyQuotedJSON,
  validJSON,
  parsedData,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

For environments which do not support lookbehinds like current Safari's one has to change the approach to two different regex patterns each featuring a capturing group for either the leading ... /([,{:])'/g ... or the trailing ... /'([:,}])/g ... character of a to be replaced single quote.

The above provided example code then changes to ...

const invalidlyQuotedJSON =
  "[{'techid':'0128','daPoints':3,'speedingPoints':3,'fleetInspectionPoints':3,'lofPoints':3,'missedTrgModules':null,'fullName':'Danny O\'Reilly','safetyInspectPoints':3,'missedTrgPoints':3,'speeding_qty':null,'safetyTotalPoints':21,'atFaultPoints':3,'atFaultAccident':null,'region':'PYEM','supervisor':'AGHATOR OSA','driverAlert':null,'status':'A'}]";

// see ... [https://regex101.com/r/gzCPaS/2]
const regXTrailingSingleQuote = /([,{:])'/g;

// see ... [https://regex101.com/r/gzCPaS/3]
const regXLeadingSingleQuote = /'([:,}])/g;

const validJSON = invalidlyQuotedJSON
  .replace(regXTrailingSingleQuote, '$1"')
  .replace(regXLeadingSingleQuote, '"$1');

const parsedData = JSON.parse(validJSON);

console.log({
  invalidlyQuotedJSON,
  validJSON,
  parsedData,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.