从文本中提取JSON

18

一个AJAX调用返回了一个包含JSON字符串的响应文本。我需要:

  1. 提取JSON字符串
  2. 修改它
  3. 然后重新插入以更新原始字符串

我不太担心步骤2和3,但我无法想出如何执行步骤1。我考虑使用正则表达式,但是由于我的JSON可能具有多个级别的嵌套对象或数组,所以不知道该如何操作。


2
你不是新手了。你尝试过什么?你的回应看起来怎么样? - Madara's Ghost
此外,正则表达式可能不是正确的工具。 - Madara's Ghost
@Truth 目前我的唯一解决方法是在响应文本中包含标记,以显示 JSON 字符串的开头和结尾。这并不值得骄傲,也不能指导答案。 - Christophe
只需简单地获取“{”字面量的firstIndex和“}”字面量的lastIndex,然后使用str.substring(firstIndex,lastIndex+1)即可满足大多数类似chatGPT这样的人工智能返回的消息。 - Ali Mert Çakar
只需要获取“{”字面量的firstIndex和“}”字面量的lastIndex,然后使用str.substring(firstIndex,lastIndex+1)即可满足像chatGPT这样的ai返回的大多数消息。 - Ali Mert Çakar
4个回答

28
你不能使用正则表达式从任意文本中提取JSON。由于正则表达式通常不足以验证JSON(除非你可以使用PCRE),它们也无法匹配JSON——如果它们能够匹配,它们也可以验证JSON。
但是,如果你知道你的JSON顶层元素始终是一个对象或数组,你可以按以下方式操作:
  • 在字符串中查找第一个开头({[)和最后一个结尾(}])括号。
  • 尝试使用JSON.parse()解析该文本块(包括括号)。如果成功,完成并返回解析结果。
  • 取前一个关闭括号并尝试解析该字符串。如果成功,再次完成。
  • 重复此过程,直到没有括号或其出现在当前开放括号之前。
  • 在步骤1之后找到第一个开放括号。如果没有找到,则该字符串不包含JSON对象/数组,你可以停止。
  • 转到步骤2。
下面是一个提取JSON对象并返回对象及其位置的函数。如果您确实需要顶级数组,也可以扩展它:
function extractJSON(str) {
    var firstOpen, firstClose, candidate;
    firstOpen = str.indexOf('{', firstOpen + 1);
    do {
        firstClose = str.lastIndexOf('}');
        console.log('firstOpen: ' + firstOpen, 'firstClose: ' + firstClose);
        if(firstClose <= firstOpen) {
            return null;
        }
        do {
            candidate = str.substring(firstOpen, firstClose + 1);
            console.log('candidate: ' + candidate);
            try {
                var res = JSON.parse(candidate);
                console.log('...found');
                return [res, firstOpen, firstClose + 1];
            }
            catch(e) {
                console.log('...failed');
            }
            firstClose = str.substr(0, firstClose).lastIndexOf('}');
        } while(firstClose > firstOpen);
        firstOpen = str.indexOf('{', firstOpen + 1);
    } while(firstOpen != -1);
}

var obj = {'foo': 'bar', xxx: '} me[ow]'};
var str = 'blah blah { not {json but here is json: ' + JSON.stringify(obj) + ' and here we have stuff that is } really } not ] json }} at all';
var result = extractJSON(str);
console.log('extracted object:', result[0]);
console.log('expected object :', obj);
console.log('did it work     ?', JSON.stringify(result[0]) == JSON.stringify(obj) ? 'yes!' : 'no');
console.log('surrounding str :', str.substr(0, result[1]) + '<JSON>' + str.substr(result[2]));

示例(在Node.js环境下执行,但也应适用于浏览器):https://paste.aeum.net/show/81/


有趣...你的链接指向一个页面,上面写着“是的,完整的正则表达式验证是可能的”! - Christophe
哦,嘿,我没有滚动到被接受的答案之后 - 但是好吧,PCRE非常强大。我不认为这些功能在JavaScript中可用。 - ThiefMaster

2

如果您想从文本中提取JSON字符串(即使它们不是有效的),您可以查看这个Gulp插件https://www.npmjs.com/package/gulp-extract-json-like。它会搜索所有看起来像JSON字符串格式的字符串。

创建一个文件夹并安装包。

mkdir project && cd project
npm install gulp gulp-extract-json-like

创建一个文件./gulpfile.js,并将以下内容放入其中:
var gulp = require('gulp');
var extractJsonLike = require('gulp-extract-json-like');

gulp.task('default', function () {
  return gulp.src('file.txt')
    .pipe(extractJsonLike())
    .pipe(gulp.dest('dist'));
});

创建一个名为./file.txt的文件,其中包含您的文本,并运行以下命令。
gulp

发现的JSON字符串将会在./dist/file.txt中。

0

如果JSON作为ajax响应的一部分返回,为什么不使用浏览器本身的JSON解析(注意陷阱)?或者使用jQuery JSON解析呢?

如果JSON与文本完全混在一起,那真的让人感到设计有问题,在我看来 - 如果可以改变它,我强烈建议这样做(即将单个JSON对象作为响应返回,并将文本作为对象的属性)。

如果不能改变,那么使用正则表达式将是一场绝对的噩梦。JSON本身非常灵活,确保准确解析不仅耗时,而且浪费资源。我可能会在开头/结尾放入内容标记,并希望一切顺利。但你将面临验证错误等问题。


很遗憾,我无法更改它。实际上,我在响应中得到的是一个包含JSON字面量参数的整个脚本。 - Christophe
我有点困惑,因为在你对这个问题的评论中,你给 JSON 字符串添加了起始/结束标记?你是如何在不能更改响应的情况下做到的? - Rob Cooper
抱歉,我的意思是我无法防止JSON与“文本”混合在一起,而这个“文本”实际上是一个脚本。 - Christophe
好的,我看到你已经接受了一个答案。如果那个答案对你有用,那太棒了,否则在问题中提供一个示例响应将给我们创造一个可行的解决方案的机会。 - Rob Cooper

0
我用自己的方法做了这件事。这肯定不是绝对可靠的,但对于改善查看带有单行JSON对象日志的能力来说,对我来说是有效的。我不是一个JavaScript开发者,所以请随意告诉我为什么这是不好的哈哈。
//PrettyPrint() will attempt to find JSON strings in the log message. If it finds them, it will replace the raw ugly JSON with pretty printted JSON
function PrettyPrint() {
    var jsonStrings = [];
    var prettyLogElement = document.getElementById('PrettyLogDisplayOnly');
    try {
        var rawLogMessage = $("textarea[id^='LogMessage']").val();
        if (rawLogMessage == null) {
            throw "Failed to extract original log message.";
        }

        jsonStrings = ExtractJsonStrings(rawLogMessage);
        
        var modifiedLogMessage = "<pre>" + rawLogMessage + "\"</pre>";

        for (const jsonString of jsonStrings) {
            try {
                var jsonObject = JSON.parse(jsonString);
                var prettyPrintJsonString = JSON.stringify(jsonObject, null, 2);
                modifiedLogMessage = modifiedLogMessage.replace(jsonString, prettyPrintJsonString);
            }
            catch (err) {
                modifiedLogMessage += "Failed to pretty print: " + jsonString;
            }
        }
    }
    catch (err) {
        if (err == null || err == undefined) {
            err = "Failed to parse.";
        }
        else
        {
            err = "Failed to parse. Details: " + err;
        }
        
        //TODO: instead of showing the error here, show it as an error banner?
        rawLogMessage = "<br/>Failed to beautify JSON objects. Details: " + err + " Displaying raw log message.<br/>" +
            "<br/>-------------------------------------------------------------------------------------<br/><br/>"
            + rawLogMessage;;

        prettyLogElement.innerHTML += rawLogMessage;
        return;
    }

    prettyLogElement.innerHTML = modifiedLogMessage;
}

function ExtractJsonStrings(rawLogMessage) {
    var jsonStrings = [];
    var locationOfCurrentCurly = -1;
    
    while (true) {
        var countOfOpenCurlyBraces = 0;
        var countOfClosedCurlyBraces = 0;

        var locationOfFirstUnescapedOpeningCurly = GetLocationOfNextUnescapedOpeningCurlyBrace(rawLogMessage, locationOfCurrentCurly + 1);
        if (locationOfFirstUnescapedOpeningCurly == -1) {
            break; //we found all the JSON strings
        }
        else
        {
            locationOfCurrentCurly = locationOfFirstUnescapedOpeningCurly;
            countOfOpenCurlyBraces++;
        }
        
        while (countOfOpenCurlyBraces != countOfClosedCurlyBraces)
        {
            if (countOfClosedCurlyBraces > countOfOpenCurlyBraces)
            {
                throw "Found more closing curly braces than opening curly braces.";
            }
            
            var startSearchAtIndex = locationOfCurrentCurly + 1
            locationOfCurrentCurly = GetLocationOfNextUnescapedCurlyBrace(rawLogMessage, startSearchAtIndex);
            if (locationOfCurrentCurly == -1) {
                throw "Failed to find the 'next' curly brace.";
            }

            var curly = rawLogMessage.charAt(locationOfCurrentCurly);
            if (curly === '{') {
                countOfOpenCurlyBraces++;
            } else if (curly === '}') {
                countOfClosedCurlyBraces++;
            } else {
                throw "Unknown character found when curly brace expected.";
            }
        }
        
        var possiblyCorrectlyFormattedJsonString = rawLogMessage.substring(locationOfFirstUnescapedOpeningCurly, locationOfCurrentCurly + 1);
        jsonStrings.push(possiblyCorrectlyFormattedJsonString);
    }

    return jsonStrings;
}

//this will only find the next opening brace {
function GetLocationOfNextUnescapedOpeningCurlyBrace(rawLogMessage, startIndex) {
    var regexNextUnescapedOpeningCurly = /(?<!\\)({)/i;
    return RegexStringExtract(rawLogMessage, startIndex, regexNextUnescapedOpeningCurly)
}

//this will find the next opening OR closing brace { }
function GetLocationOfNextUnescapedCurlyBrace(rawLogMessage, startIndex) {
    var regexNextUnescapedCurly = /(?<!\\)({|})/i;
    return RegexStringExtract(rawLogMessage, startIndex, regexNextUnescapedCurly)
}

function RegexStringExtract(stringToSearch, startIndex, regex) {
    var substring = stringToSearch.substring(startIndex);
    var regexMatch = regex.exec(substring);
    if (regexMatch) {
        return startIndex + regexMatch.index;
    }
    else {
        return -1;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接