JSON解析实战指南:Python用法与Java、Go、Rust等主流语言选型
2026/9/9 3:08:19 网站建设 项目流程

前阵子帮团队处理一批接口数据,同事把几十万条记录导出成JSON文件,结果接手的同学只会用Python,另一个小组却要Java直接消费这批数据。折腾了半天,我索性把主流的JSON解析方案都过了一遍,才发现这个看似入门级的话题,真展开聊能写不少东西。JSON(JavaScript Object Notation)现在几乎就是数据交换的默认格式,配置文件、API返回、日志采集、AI工作流参数、甚至小说阅读器的书源配置,到处都是它的影子。这篇文章我先聊聊自己在Python里的用法和踩过的坑,再逐个拆解 JavaScript、Java、Go、C#、PHP、Rust 这些语言各自的解析方式,帮你搞清楚不同项目里到底该选谁。

1. 先搞清楚JSON解析到底在干什么

1.1 把字符串变回数据结构,这才是核心

很多人一上来就背API,什么json.loadsJSON.parse,其实没搞明白底层在干嘛。JSON本质上就是一串有格式的文本,语言要做的第一件事,是把这个纯文本拆成内存里的结构体:Python里的字典和列表、Java里的Map和List、Go里的struct和slice。这个"拆"的过程叫反序列化,也就是解析。反过来,把内存对象输出成字符串,叫序列化,也就是生成JSON。

所以不管什么语言,核心矛盾都一样:如何把文本里的括号、引号、冒号,翻译成当前语言能直接操作的数据结构。动态语言通常翻译成字典/对象,静态强类型语言要么翻译成无类型的Map,要么按你定义好的类/结构体去填充字段。理解这一层,后面所有语言的API看起来都不过是同一个思路的不同皮囊。

1.2 为什么不同语言解法差别这么大

同样是解析{"name":"张三","age":18},Python一行dict(json.loads(...)),Java你得引入Jackson或者Gson,Go需要定义结构体,Rust甚至要先derive几个宏。差别不在JSON本身,而在语言设计哲学。

动态语言追求开发效率,解析结果天然就是灵活的字典/对象,改字段名、增删字段都不需要动类型定义。静态语言追求类型安全,希望编译期就能发现字段写错、类型不匹配的问题,所以解析通常要绑定到具体的类或结构体,代价是样板代码多一些。没有孰优孰劣,只有场景合不合适。接下来我把Python的日常用法讲透,再横向对比其他语言,这样你脑子里会有一个很清晰的坐标系。

2. Python的JSON解析,我的日常用法和隐藏坑

2.1 基础四件套必须刻进肌肉记忆

Python的json模块就四个核心方法,我用烂了:

import json # 字符串转对象 data = json.loads('{"name": "张三", "age": 18}') print(data["name"]) # 张三 # 对象转字符串 json_str = json.dumps(data, ensure_ascii=False) print(json_str) # {"name": "张三", "age": 18} # 文件读取解析 with open("data.json", "r", encoding="utf-8") as f: data = json.load(f) # 对象写入文件 with open("out.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)

loadsdumps处理字符串,loaddump处理文件对象,别搞混。我见过有人把json.load扔进json.loads里报AttributeError,就是因为这两个名字长得太像,其实一个接收文件、一个接收字符串。

2.2 类型映射表,记住这些不会翻车

JSON类型Python类型注意事项
objectdict键永远是字符串
arraylist对应json数组,下标从0开始
stringstr默认会转义非ASCII字符
number (int)int大整数能直接表示
number (real)float精度可能丢失
true/falseTrue/False注意首字母大写
nullNone不是Null也不是none

truefalsenull在Python里是TrueFalseNone,反序列化时自动转了。但如果你手动拼JSON字符串,可别写成True,否则对方解析会直接报错。这个细节在写爬虫拼接请求体时特别容易踩。

2.3 实战案例:labelme多边形JSON怎么转txt

热搜里有个词叫"labelme多边形json转txt",这正好是JSON解析的经典场景。用labelme标注图像后,每张图生成一个同名JSON,里面有个shapes数组,每个元素包含labelpoints字段。我需要把这些多边形坐标批量提取出来,转成YOLO或普通文本格式。

import json with open("annotation.json", "r", encoding="utf-8") as f: data = json.load(f) for shape in data["shapes"]: label = shape["label"] points = shape["points"] # points是二维数组,例如 [[x1,y1],[x2,y2],...] coords = " ".join([f"{p[0]:.2f},{p[1]:.2f}" for p in points]) print(f"{label} {coords}")

这段代码的核心逻辑就是:先json.load把整个标注文件读成字典,然后一层层往下取。你用Python写一次,换Java、Go无非就是把取字段的方式改一改,思路完全一样。类似的还有书源JSON、订阅源JSON、ComfyUI工作流JSON,处理模式都是三步走:读取文本、解析成对象、按字段遍历。

2.4 小技巧和隐藏坑,每条都是实测换来的

第一,ensure_ascii=False一定要记住json.dumps默认会把中文转成\uXXXX,看的你头大。加上这个参数,输出才是正常人能读的中文。文件读写时encoding="utf-8"也别忘了,Windows下经常因为默认编码不对导致乱码。

第二,大JSON文件不要一把梭。如果你处理的是几百MB甚至几个GB的日志、导出数据,json.load()会把整个文件读进内存,直接爆内存。这时候要么改用ijson做流式解析,要么先判断数据结构能不能按行切分。比如日志文件每行一个JSON对象,就老老实实按行读:

import json results = [] with open("big.log", "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: data = json.loads(line) results.append(data)

第三,json.dumps遇到不认识的类型会报错。比如字典里有个datetime对象,直接dumps会告诉你Object of type datetime is not JSON serializable。解决办法是给default参数传一个自定义转换函数:

import json from datetime import datetime def convert(obj): if isinstance(obj, datetime): return obj.isoformat() raise TypeError(f"Type {type(obj)} not serializable") data = {"time": datetime.now()} print(json.dumps(data, default=convert))

第四,重复键不会报错json.loads('{"a":1,"a":2}')会静默取最后一个值。如果上游数据不可信,建议解析前做一次校验,或者用object_pairs_hook去检测重复键,别等线上出bug再排查。

3. 除了Python,这些语言解析JSON也很顺手

3.1 JavaScript/TypeScript:真正的"嫡系"

JSON全称就是JavaScript Object Notation,所以JS解析JSON几乎是零成本。浏览器和Node.js都内置了JSON对象,直接JSON.parse()JSON.stringify(),不需要装任何依赖:

// 浏览器或Node.js环境 const obj = JSON.parse('{"name":"张三","age":18}'); console.log(obj.name); // 张三 const str = JSON.stringify(obj); console.log(str);

在实际工程里,常配合文件操作一起用。Node.js里读取JSON文件要注意编码,最好显式指定utf8

const fs = require('fs'); const data = JSON.parse(fs.readFileSync('data.json', 'utf8'));

写回文件时,想要格式化输出就用JSON.stringify(obj, null, 2),第二个参数是替换函数,第三个是缩进空格数,这个2是我最常用的。

TypeScript则更进一步,可以在解析后做类型断言:

interface Person { name: string; age: number; } const obj = JSON.parse('{"name":"张三","age":18}') as Person; console.log(obj.name.toUpperCase());

不过要注意,as Person只是编译期类型断言,运行时JSON里如果缺了name字段,照样是undefined,不会像Java、Go那样强校验。TypeScript的"类型安全"更多是开发期的保障,不是运行期的护身符。

3.2 Java:Jackson和Gson,到底选谁

Java没有内置JSON解析标准库,早年最流行的是Google的Gson和 FasterXML的Jackson。现在企业级项目里Jackson基本是事实标准,Spring Boot默认集成也是它。

Jackson核心用法如下:

import com.fasterxml.jackson.databind.ObjectMapper; ObjectMapper mapper = new ObjectMapper(); // JSON字符串转Map Map<String, Object> map = mapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {}); // JSON字符串转Java对象(推荐) Person person = mapper.readValue(jsonStr, Person.class); // Java对象转JSON字符串 String jsonStr = mapper.writeValueAsString(person);

如果你的数据字段很多,我更推荐直接定义POJO:

public class Person { private String name; private int age; // getter和setter必须写,Jackson靠反射和getter/setter工作 }

Java解析的典型痛点有几个:一是ObjectMapper建议复用同一个实例,因为它是线程安全的,频繁new性能损耗很大;二是日期字段要单独配置格式;三是如果JSON里某个字段在Java类里不存在,默认会抛UnrecognizedPropertyException,这时候可以配置:

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

Gson的用法更轻量:

Gson gson = new Gson(); Person person = gson.fromJson(jsonStr, Person.class); String json = gson.toJson(person);

Gson对没有getter/setter的类也能处理,反射得更彻底,但性能略逊于Jackson。我的建议是:Spring全家桶项目直接用Jackson,不需要引入额外依赖;独立的小工具、Android项目可以考虑Gson。

3.3 Go:结构体标签把字段映射写脸上

Go的encoding/json是标准库里最常用的包,它的特点是通过struct tag来声明字段映射关系,一眼就能看出来JSON字段叫什么、选填还是必填:

package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int `json:"age,omitempty"` City string `json:"city,omitempty"` } func main() { jsonStr := `{"name":"张三","age":18}` var p Person err := json.Unmarshal([]byte(jsonStr), &p) if err != nil { fmt.Println("解析失败:", err) return } fmt.Printf("%+v\n", p) // 结构体转JSON out, _ := json.Marshal(p) fmt.Println(string(out)) }

Go解析JSON有几个必须注意的地方。第一,结构体字段必须首字母大写,否则包外不可见,json.Unmarshal根本填不进去。这大概是新手最容易踩的坑。第二,omitempty的意思是字段值为零值时序列化时省略掉,这很适合处理可选字段。第三,解析数字默认是float64,如果你直接断言成整数类型,会报错。这是Go被吐槽最多的一点。

如果你不想定义结构体,也可以用map[string]interface{},但使用体验很差,类型断言写到你怀疑人生。所以在Go里,能定义结构体就定义结构体,不要偷懒。

3.4 C#:Newtonsoft.Json 与 System.Text.Json 的迭代

C#阵营曾经是Newtonsoft.Json(也叫Json.NET)的天下,功能和灵活性都是顶级的,几乎无所不能。从.NET Core 3.0开始,微软官方推出了System.Text.Json,性能比Newtonsoft好不少,现在的新项目我通常优先用它。

using System.Text.Json; var jsonStr = "{\"name\":\"张三\",\"age\":18}"; var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var person = JsonSerializer.Deserialize<Person>(jsonStr, options); public class Person { public string Name { get; set; } public int Age { get; set; } }

System.Text.Json默认是大小写敏感的,所以配置PropertyNameCaseInsensitive = true可以避免JSON里name和C#属性Name对不上的烦恼。如果还想要和JSON字段名完全不同的映射,用[JsonPropertyName("full_name")]特性。

Newtonsoft.Json的优势在于生态成熟,很多老项目都在用,网上资料也多。如果你的项目是.NET 6+且不需要老库的特殊功能,直接用System.Text.Json就够了;如果你是维护Legacy项目,别纠结,继续用Newtonsoft就行。能用新库别守旧,能少一个依赖是一个依赖。

3.5 PHP:json_decode一行搞定,但不代表没坑

PHP的JSON支持非常"无脑":

$jsonStr = '{"name":"张三","age":18}'; $data = json_decode($jsonStr, true); echo $data['name']; // 不传第二个参数,得到的是对象 $obj = json_decode($jsonStr); echo $obj->name;

第二个参数传true会转成关联数组,不传则得到stdClass对象,一般处理数组比对象方便,所以我习惯传true

PHP解析JSON最大的坑是静默失败。如果JSON格式有问题,json_decode返回null,不报任何异常。这时候你继续操作$data['name'],又是一通报错。判断是否解析成功,必须检查错误:

$data = json_decode($jsonStr, true); if (json_last_error() !== JSON_ERROR_NONE) { echo 'JSON解析错误: ' . json_last_error_msg(); }

PHP 7.3以后可以传JSON_THROW_ON_ERROR标志,直接抛异常:

try { $data = json_decode($jsonStr, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { echo '解析失败: ' . $e->getMessage(); }

我强烈建议代码里加上异常处理,不然排查问题全靠猜。

3.6 Rust:serde_json的强类型玩法

Rust在JSON解析领域几乎是靠serdeserde_json打天下。serde是一个序列化/反序列化框架,支持JSON、YAML、TOML、BSON等多种格式,serde_json是把serde和JSON绑定起来的库。

use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct Person { name: String, #[serde(default)] age: u8, } fn main() -> Result<(), Box<dyn std::error::Error>> { let json_str = r#"{"name":"张三","age":18}"#; let p: Person = serde_json::from_str(json_str)?; println!("{:?}", p); let out = serde_json::to_string(&p)?; println!("{}", out); Ok(()) }

Rust的解析有两个明显优势:一是字段缺失可以在编译/运行期直接报错,#[serde(default)]可以给缺失字段提供默认值;二是性能和内存安全都很出色。代价是写起来比较啰嗦,每个结构体都要写derive宏。如果你在写CLI工具、网络服务或者对性能极度敏感的场景,Rust这套组合拳值得投入成本。

3.7 终端里的jq:不算语言但很能打

严格来说jq不是编程语言,但它是一个专门处理JSON的命令行工具,几乎每个搞数据的同学都应该装上。用它可以在写任何脚本前,先快速检查JSON结构:

# 格式化并高亮显示 cat data.json | jq . # 提取某个字段 cat data.json | jq '.name' # 遍历数组并取多个字段 cat data.json | jq '[.items[].name]'

我的习惯是处理一个陌生JSON之前,先jq .看一眼结构再写代码,比打开编辑器看半天有效得多。它只能当辅助工具,替代不了编程语言里那些业务逻辑处理,但配合任何语言都很好用。

4. 跨语言解析JSON的常见坑与排查技巧

4.1 高频报错速查表

我在实际项目里整理了一张速查表,遇到类似报错直接对号入座:

语言/场景典型报错常见原因解决办法
Pythonjson.decoder.JSONDecodeError: Expecting value: line 1 column 2字符串不是合法JSON,比如空字符串、BOM头、多余逗号先打印原始字符串,检查空白字符和编码
PythonTypeError: Object of type datetime is not JSON serializable序列化时遇到非JSON类型dumpsdefault函数
Java/JacksonMismatchedInputException: Cannot deserialize value of type int from StringJSON里数字被写成字符串,如"age":"18"在POJO的setter里做转换,或改用String接收再处理
Java/启动器java.io.IOException: Unable to read version json目标JSON文件为空、下载不完整或格式损坏检查文件是否完整,重新获取,确认路径正确
Gojson: cannot unmarshal string into Go struct field Person.age of type int同上,字符串与int类型不匹配改结构体字段类型,或写自定义UnmarshalJSON
Gojson: cannot unmarshal object into Go value of type []Something顶层是对象而不是数组jq .确认JSON结构再定义类型
Rustmissing field \name``反序列化时必填字段缺失补字段或用#[serde(default)]给默认值
Rust/部分框架failed to deserialize the json body into the target type: input: missing fie...请求体里缺字段,或字段名拼写不一致校验请求体schema,检查字段名大小写
C#JsonException: The JSON value could not be converted to System.Int32类型不匹配,比如浮点数塞进int属性改用longdouble,或使用JsonSerializerOptions.NumberHandling
PHPjson_decode返回null且无异常JSON格式错误json_last_error_msg()看具体错误原因

4.2 编码、BOM与日期格式,这三个坑最隐蔽

编码问题最容易让人崩溃。很多Windows下生成的JSON文件自带UTF-8 BOM头,python json.load会直接报Unexpected UTF-8 BOM,Java的Jackson也可能解析失败。解决方案很简单,读取文件时跳过BOM或指定编码:

# Python读取带BOM的UTF-8文件 with open("data.json", "r", encoding="utf-8-sig") as f: data = json.load(f)
// Java使用InputStreamReader指定UTF-8 ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(new InputStreamReader(inputStream, StandardCharsets.UTF_8));

日期格式不统一则是大型项目中永远的痛。有人用"created":"2026-05-01 12:00:00",有人用"created":"2026-05-01T12:00:00Z",还有人用时间戳"created":1777536000。解析前先约定好规范比啥技巧都重要。如果历史数据没法改,就得在代码里写兼容逻辑,逐个判断类型再转换。

数字精度也值得一提。JSON里的整数在不同语言里会被解析成不同的类型:Python和Go把整数存成int,JavaScript的JSON.parse把所有数字都当float64处理,导致超过Number.MAX_SAFE_INTEGER的大整数精度丢失。如果你处理的是长ID(比如雪花算法生成的ID),用JS解析就危险了,要么转成字符串、要么用BigInt,别等数据对不上才想起来。

4.3 性能与选型心得,我的实际体验

最后聊聊选型。很多人问"到底用哪个语言解析JSON最好",我的答案永远是看场景:

  • 数据分析、小脚本、爬虫、AI训练数据处理:无脑Python,开发效率无敌,json模块够用,缺性能再加orjson(一个更快的第三方库)。
  • Web前端、Node.js服务:JavaScript/TypeScript,内置JSON对象,集成度最高,几乎没有额外学习成本。
  • 企业后端、微服务:Java用Jackson,C#用System.Text.Json,两者都是生态完善、性能在线、维护成本可控的选择。
  • 高性能API、云原生基础设施、CLI工具:Go,静态编译、内存占用低、并发能力强,struct tag的写法越用越顺手。
  • 系统级组件、对安全和性能要求苛刻的场景:Rust + serde,编译期就把类型问题全堵死。
  • 快速做Web接口、临时脚本:PHP的json_decode是真的快,前提是你记得做错误处理。

性能对比上,单次解析的差距在小数据量下几乎无感知,但在每秒几十万次请求的高并发服务里就很明显了。Node.js的V8引擎、Go、Rust通常响应最快,Python和PHP需要额外优化,比如用更快的第三方解析库、避免无谓的对象拷贝。

再补一个我自己习惯的工作流:拿到任何一批JSON数据,先jq .看结构,再写个最小的测试文件跑通解析,最后才去封装正式逻辑。这套流程帮我避开了无数低级错误。JSON解析本身不复杂,复杂的是数据不规整、字段对不上、编码乱套。你把这个基本功打扎实了,不管切到什么语言,遇到问题都不慌。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询