
在处理包含多个json对象的数组时,我们经常需要将这些对象逐一提取出来进行后续处理。虽然有专门的json解析库(如jackson, gson)可以高效完成此任务,但在某些特定场景下,例如数据格式相对固定、需要快速原型验证或避免引入额外依赖时,正则表达式提供了一种灵活且直接的解决方案。
挑战:直接分割的局限性
一个常见的想法是使用字符串的split("},")方法来分割JSON数组。然而,这种方法存在明显缺陷。如果JSON对象内部包含嵌套的JSON对象或数组,"}"字符可能会出现在对象内部,导致错误的分割。例如,{"a": {"b": 1}, "c": 2}中的"}"就会被错误地识别为分隔符。因此,我们需要一个更智能、能够识别完整JSON对象边界的模式。
核心解决方案:正则表达式
对于格式化的JSON数组,我们可以利用其结构特点来构建一个精确的正则表达式。假设JSON数组中的每个对象都以特定的缩进开始和结束,例如:
[
{
"name": "User1",
"gender": "M"
},
{
"name": "User2",
"gender": "F"
}
]在这种情况下,每个独立JSON对象都以{开头,并以}结尾,且它们都位于特定缩进(例如,行首的四个空格)之后。基于此,我们可以设计如下正则表达式:
(?sm)(?<=^ )\{.*?(?<=^ )}让我们分解这个正则表达式的各个部分:
立即学习“Java免费学习笔记(深入)”;
- (?sm):这是两个模式修饰符的组合:
- s (DOTALL):使.(点)匹配包括换行符在内的所有字符。这对于匹配跨越多行的JSON对象至关重要。
- m (MULTILINE):使^和$分别匹配每一行的开始和结束,而不仅仅是整个字符串的开始和结束。
- (?正向后行断言(Positive Lookbehind)。它要求匹配的内容必须紧跟在“行首^和四个空格` `”之后。这确保我们只匹配那些以指定缩进开始的JSON对象。
- \{:匹配字面意义上的开花括号{。需要转义,因为{在正则表达式中是特殊字符。
- .*?:这是一个非贪婪匹配模式。它匹配任意数量的任意字符(包括换行符,因为有s修饰符),但尽可能少地匹配,直到遇到下一个模式。
- (?正向后行断言,它要求匹配的闭花括号}必须紧跟在“行首^和四个空格` `”之后。这与前面的开花括号断言共同确保我们匹配的是一个完整的、以特定缩进开始和结束的JSON对象。
Java代码实现
下面是使用Java Pattern和Matcher类来应用此正则表达式并提取JSON对象的示例代码:
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.toList;
public class JsonArrayExtractor {
public static void main(String[] args) {
String jsonInput = "[\n" +
" {\n" +
" \"name\": \"User1\",\n" +
" \"gender\": \"M\"\n" +
" },\n" +
" {\n" +
" \"name\": \"User2\",\n" +
" \"gender\": \"F\"\n" +
" }\n" +
"]";
// 定义正则表达式模式
// (?sm) 开启DOTALL和MULTILINE模式
// (?<=^ ) 正向后行断言:匹配内容前必须是行首和四个空格
// \{ 匹配字面量 '{'
// .*? 非贪婪匹配任意字符(包括换行符)
// (?<=^ )} 正向后行断言:匹配内容后必须是行首和四个空格,然后是字面量 '}'
Pattern pattern = Pattern.compile("(?sm)(?<=^ )\\{.*?(?<=^ )}");
// 使用Matcher找到所有匹配项
List jsonObjects = pattern.matcher(jsonInput)
.results() // 获取所有匹配结果流
.map(MatchResult::group) // 提取每个匹配的字符串
// 清理匹配到的JSON字符串中的多余空白和换行符,使其成为紧凑的单行JSON
// [\s\n]* 匹配任意空白字符(包括换行)0次或多次
// (?!\",) 负向先行断言,确保不匹配紧跟在双引号和逗号后的空白,以避免破坏字符串内容
.map(str -> str.replaceAll("[\s\n]*(?!\",)", ""))
.collect(toList()); // 收集到List中
// 打印提取出的JSON对象
jsonObjects.forEach(System.out::println);
}
} 输出结果:
{"name":"User1","gender":"M"}
{"name":"User2","gender":"F"}代码解析:
- Pattern.compile("..."): 编译正则表达式模式,使其可以被高效地重复使用。
- pattern.matcher(jsonInput): 创建一个Matcher对象,用于在jsonInput字符串中查找匹配项。
-
.results(): Java 9+ 引入的方法,返回一个Stream
,其中包含所有非重叠的匹配结果。 - .map(MatchResult::group): 从每个MatchResult中提取匹配到的完整字符串。
- *`.map(str -> str.replaceAll("[\s\n](?!\",)", ""))**: 这一步是关键的后处理。原始匹配结果可能包含由于(?s)模式导致的换行符和额外的缩进空白。此replaceAll`操作旨在移除JSON字符串中除了键值对之间必要的逗号之外的所有不必要的空白和换行符,将JSON格式化为紧凑的单行形式。
- [\s\n]*: 匹配任意数量的空白字符(包括空格、制表符、换行符)。
- (?!\",): 这是一个负向先行断言。它的作用是确保[\s\n]*匹配到的空白字符不是紧跟在"和,(即",)之后。这可以防止我们将JSON字符串值内部的空格或换行符意外删除,例如"description": "This is a \n multi-line description"。
-
.collect(toList()): 将处理后的JSON字符串收集到一个List
中。
注意事项与优化
-
格式依赖性:上述正则表达式高度依赖于JSON输入的特定格式,特别是每个对象前的固定缩进(本例中是四个空格)。如果JSON的缩进发生变化(例如,两个空格、制表符,或根本没有缩进),正则表达式需要相应调整。
- 如果缩进不固定,但每个对象仍独立成行,可以尝试更通用的模式,如(?sm)(?
- 如果JSON是紧凑格式(无换行和缩进),则这种基于行首和缩进的正则表达式将不再适用。在这种情况下,通常需要更复杂的逻辑,或者直接使用JSON解析库。
- 嵌套对象:此正则表达式在处理嵌套JSON对象时表现良好,因为.*?会非贪婪地匹配直到找到最外层的匹配闭花括号。然而,如果嵌套对象也具有相同的缩进模式,且其内部的}也满足后行断言,可能会导致意外行为。对于复杂或不确定的嵌套结构,使用专门的JSON解析库(如Jackson、Gson)是更健壮的选择,它们能正确处理JSON的语法树。
- 性能考虑:虽然正则表达式对于特定模式匹配非常强大,但对于非常大的JSON字符串或需要高吞吐量的场景,其性能可能不如流式JSON解析器。JSON解析库通常采用SAX或DOM模型,能够更高效地处理大型JSON数据。
- 错误处理:本教程侧重于成功提取,但实际应用中应考虑JSON格式不规范或匹配失败的情况,并加入适当的错误处理机制。
总结
利用正则表达式从格式化的JSON数组中提取独立JSON对象是一种灵活且直接的方法,特别适用于数据格式相对固定、对性能要求不极致的场景。通过精确构造正则表达式,我们可以有效地识别并分离出每个JSON实体。然而,对于复杂的、动态的或不确定格式的JSON数据,强烈建议使用专业的JSON解析库,以确保解析的健壮性和准确性。在选择工具时,应根据具体需求和JSON数据的特点权衡利弊。










