XML和JSON是两种经常在网络使用的数据表示格式,这里我们介绍如何使用Java读写XML和JSON。
一、XML 概述
1、XML简介
我们都知道对象是不能在网络中直接传输的,不过还有补救的办法。
XML(Extensible Markup Language)可扩展标记语言,本身就被设计用来存储数据,任何一个对象都可以用XML来描述。XML是可以作为对象信息的载体在网络中传输,因为它是文本形式的。
例如,一个描述书籍的XML文档可能如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
XML有几个特点:一是纯文本,默认使用UTF-8编码,二是可嵌套,适合表示结构化数据。如果把XML内容存为文件,那么它就是一个XML文件,例如book.xml。此外,XML内容经常通过网络作为消息传输。
2、XML的结构
XML有固定的结构,首行必定是<?xml version="1.0"?>,可以加上可选的编码。紧接着,如果以类似<!DOCTYPE note SYSTEM "book.dtd">声明的是文档定义类型(DTD:Document Type Definition),DTD是可选的。接下来是XML的文档内容,一个XML文档有且仅有一个根元素,根元素可以包含任意个子元素,元素可以包含属性,例如,<isbn lang="CN">1234567</isbn>包含一个属性lang="CN",且元素必须正确嵌套。如果是空元素,可以用<tag/>表示。
由于使用了<、>以及引号等标识符,如果内容出现了特殊符号,需要使用&???;表示转义。
例如,Java<tm>必须写成:
<name>Java<tm></name>
常见的特殊字符如下:
字符 | 表示 |
< | < |
> | > |
& | & |
" | " |
' | ' |
格式正确的XML(Well Formed)是指XML的格式是正确的,可以被解析器正常读取。而合法的XML是指,不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证。
DTD文档可以指定一系列规则,例如:
- 根元素必须是
book
-
book
元素必须包含name
,author
等指定元素 -
isbn
元素必须包含属性lang
- ...
如何验证XML文件的正确性呢?最简单的方式是通过浏览器验证。可以直接把XML文件拖拽到浏览器窗口,如果格式错误,浏览器会报错。
和结构类似的HTML不同,浏览器对HTML有一定的“容错性”,缺少关闭标签也可以被解析,但XML要求严格的格式,任何没有正确嵌套的标签都会导致错误。
XML是一个技术体系,除了我们经常用到的XML文档本身外,XML还支持:
- DTD和XSD:验证XML结构和数据是否有效;
- Namespace:XML节点和属性的名字空间;
- XSLT:把XML转化为另一种文本;
- XPath:一种XML节点查询语言;
- ...
实际上,XML的这些相关技术实现起来非常复杂,在实际应用中很少用到,通常了解一下就可以了。
3、DOM
因为XML是一种树形结构的文档,它有两种标准的解析API:
- DOM:一次性读取XML,并在内存中表示为树形结构。
- SAX:以流的形式读取XML,使用事件回调。
我们先来看如何使用DOM来读取XML。
DOM是Document Object Model的缩写,DOM模型就是把XML结构作为一个树形结构处理,从根节点开始,每个节点都可以包含任意个子节点。
我们以下面的XML为例:
<?xml version="1.0" encoding="UTF-8" ?>
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
如果解析为DOM结构,它大概长这样:
注意到最顶层的document代表XML文档,它是真正的“根”,而<book>虽然是根元素,但它是document的一个子节点。
Java提供了DOM API来解析XML,它使用下面的对象来表示XML的内容:
- Document:代表整个XML文档;
- Element:代表一个XML元素;
- Attribute:代表一个元素的某个属性。
使用DOM API解析一个XML文档的代码如下:
InputStream input = Main.class.getResourceAsStream("/book.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);
DocumentBuilder.parse()用于解析一个XML,它可以接收InputStream,File或者URL,如果解析无误,我们将获得一个Document对象,这个对象代表了整个XML文档的树形结构,需要遍历以便读取指定元素的值:
void printNode(Node n, int indent) {
for (int i = 0; i < indent; i++) {
System.out.print(' ');
}
switch (n.getNodeType()) {
case Node.DOCUMENT_NODE: // Document节点
System.out.println("Document: " + n.getNodeName());
break;
case Node.ELEMENT_NODE: // 元素节点
System.out.println("Element: " + n.getNodeName());
break;
case Node.TEXT_NODE: // 文本
System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
break;
case Node.ATTRIBUTE_NODE: // 属性
System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
break;
default: // 其他
System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
}
for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
printNode(child, indent + 1);
}
}
解析结构如下:
Document: #document
Element: book
Text: #text =
Element: name
Text: #text = Java核心技术
Text: #text =
Element: author
Text: #text = Cay S. Horstmann
Text: #text =
...
对于DOM API解析出来的结构,我们从根节点Document出发,可以遍历所有子节点,获取所有元素、属性、文本数据,还可以包括注释,这些节点被统称为Node,每个Node都有自己的Type,根据Type来区分一个Node到底是元素,还是属性,还是文本,等等。
使用DOM API时,如果要读取某个元素的文本,需要访问它的Text类型的子节点,所以使用起来还是比较繁琐的。
4、SAX
使用DOM解析XML的优点是用起来省事,但它的主要缺点是内存占用太大。
另一种解析XML的方式是SAX。SAX是Simple API for XML的缩写,它是一种基于流的解析方式,边读取XML边解析,并以事件回调的方式让调用者获取数据。因为是一边读一边解析,所以无论XML有多大,占用的内存都很小。
SAX解析会触发一系列事件:
- startDocument:开始读取XML文档;
- startElement:读取到了一个元素,例如
<book>
; - characters:读取到了字符;
- endElement:读取到了一个结束的元素,例如
</book>
; - endDocument:读取XML文档结束。
如果我们用SAX API解析XML,Java代码如下:
InputStream input = Main.class.getResourceAsStream("/book.xml");
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(input, new MyHandler());
关键代码SAXParser.parse()除了需要传入一个InputStream外,还需要传入一个回调对象,这个对象要继承自DefaultHandler:
class MyHandler extends DefaultHandler {
public void startDocument() throws SAXException {
print("start document");
}
public void endDocument() throws SAXException {
print("end document");
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
print("start element:", localName, qName);
}
public void endElement(String uri, String localName, String qName) throws SAXException {
print("end element:", localName, qName);
}
public void characters(char[] ch, int start, int length) throws SAXException {
print("characters:", new String(ch, start, length));
}
public void error(SAXParseException e) throws SAXException {
print("error:", e);
}
void print(Object... objs) {
for (Object obj : objs) {
System.out.print(obj);
System.out.print(" ");
}
System.out.println();
}
}
运行SAX解析代码,可以打印出下面的结果:
start document
start element: book
characters:
start element: name
characters: Java核心技术
end element: name
characters:
start element: author
...
如果要读取<name>节点的文本,我们就必须在解析过程中根据startElement()和endElement()定位当前正在读取的节点,可以使用栈结构保存,每遇到一个startElement()入栈,每遇到一个endElement()出栈,这样,读到characters()时我们才知道当前读取的文本是哪个节点的。可见,使用SAX API仍然比较麻烦。
总结:
- XML使用嵌套结构的数据表示方式,支持格式验证;
- XML常用于配置文件、网络消息传输等;
- Java提供的DOM API可以将XML解析为DOM结构,以Document对象表示;
- DOM可在内存中完整表示XML数据结构;
- DOM解析速度慢,内存占用大;
- SAX是一种流式解析XML的API;
- SAX通过事件触发,读取速度快,消耗内存少;
- 调用方必须通过回调方法获得解析过程中的数据;
二、XML 序列化和反序列化
序列化指把对象通过流的方式存储到文件中,反序列化则是指把文件中的字节内容读出来并还原成 Java 对象。
1、XmlSerializer类
XmlSerializer类:将对象序列化到 XML 文档中和从 XML 文档中反序列化对象。XmlSerializer 使您得以控制如何将对象编码到 XML 中。
XML 序列化是将对象的公共属性 (Property)(如Student的Name属性)和字段转换为序列格式(这里是指 XML)以便存储或传输的过程。反序列化则是从 XML 输出中重新创建原始状态的对象。因此,可以将序列化视为将对象的状态保存到流或缓冲区的方法。
例如,ASP.NET 使用 XmlSerializer 类对 XML Web services 消息进行编码。
看一段最简单的Xml序列化代码:
class Program
{
static void Main(string[] args)
{
int i = 10;
//声明Xml序列化对象实例serializer
XmlSerializer serializer = new XmlSerializer(typeof(int));
//执行序列化并将序列化结果输出到控制台
serializer.Serialize(Console.Out, i);
Console.Read();
}
}
上面代码对int i进行了序列化,并将序列化的结果输出到了控制台,输出结果如下:
<?xml version="1.0" encoding="gb2312"?>
<int>10</int>
可以将上述序列化的xml进行反序列化,如下代码:
static void Main(string[] args)
{
using (StringReader rdr = new StringReader(@"<?xml version=""1.0"" encoding=""gb2312""?>
<int>10</int>"))
{
//声明序列化对象实例serializer
XmlSerializer serializer = new XmlSerializer(typeof(int));
//反序列化,并将反序列化结果值赋给变量i
int i = (int)serializer.Deserialize(rdr);
//输出反序列化结果
Console.WriteLine("i = " + i);
Console.Read();
}
}
以上代码用最简单的方式说明了xml序列化和反序列化的过程,.Net系统类库为我们做了大量的工作,序列化和反序列化都非常简单。但是在现实中业务需求往往比较复杂,不可能只简单的序列化一个int变量,显示中我们需要对复杂类型进行可控制的序列化。
[XmlRoot("cat")]
//要求不序列化Speed属性
[XmlIgnore]
[XmlAttribute]
[XmlElement]
可以使用XmlElement指定属性序列化为子节点(默认情况会序列化为子节点);或者使用XmlAttribute特性制定属性序列化为Xml节点的属性;还可以通过XmlIgnore特性修饰要求序列化程序不序列化修饰属性。
2、对象序列化和反序列化
首先我们先定义实体类:
public class People
{
//XmlAttribute:指定XmlSerializer将该类成员序列化为XML属性
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public int Age { get; set; }
}
[XmlRoot]
public class Student : People
{
//定义SClass属性的序列化为Student节点的属性
[XmlElement]
public string SClass { get; set; }
[XmlElement]
public int Number { get; set; }
}
第一步:将实体类序列化为XML文档,代码如下:
Student stu = new Student() { Age = 12, Number = 23, Name = "张三", SClass = "高一(2)班" };
XmlSerializer ser = new XmlSerializer(typeof(Student));
StringWriter writer = new StringWriter();
ser.Serialize(writer,stu);
MessageBox.Show(writer.ToString());
在弹出框,出现的结果是:
这样,我们就序列化成功了。
第二步:现在我们来进行反序列化测试:
//将Xml反序列为Student对象
StringReader reader = new StringReader(writer.ToString());
//Deserialize反序列化指定TextReader包含的Xml文档,当然,不仅仅可以是TextReader,还可以是Stream等等,具体看起构造函数参数即可知道
Student stu2= (Student)ser.Deserialize(reader);
我们用上面得到XML数据进行反序列化测试。查看运行结果,ok!
3、列表序列化和反序列化
和上面一样,序列化学生列表(People类和Student类和上面代码一样)。
List<Student> stuList = new List<Student>();
stuList.Add(new Student() { Age = 10, Number = 1, Name = "Tom", SClass = "Class One" });
stuList.Add(new Student() { Age = 11, Number = 2, Name = "Jay", SClass = "Class Two" });
stuList.Add(new Student() { Age = 12, Number = 3, Name = "Pet", SClass = "Class One" });
stuList.Add(new Student() { Age = 13, Number = 4, Name = "May", SClass = "Class Three" });
stuList.Add(new Student() { Age = 14, Number = 5, Name = "Soy", SClass = "Class Two" });
//序列化
XmlSerializer ser = new XmlSerializer(typeof(List<Student>));
StringWriter writer = new StringWriter();
//将学生列表序列化为Xml数据
ser.Serialize(writer, stuList);
//反序列化
//要先将构造StringReader,作为Deserialize()的初始化参数
StringReader reader = new StringReader(writer.ToString());
//别忘了从Object到List<Student>,否则会报错。。
List<Student> stuList2 = (List<Student>)ser.Deserialize(reader);
运行结果是(注意:根是ArrayOfStudent不是Student了):
4、字典序列化和反序列化
在XmlSerializer中,不支持Dirctionary<>类型的对象,所以在序列化这种最常见类型的时候,只能按照它的格式先创建一个可以序列化的类型,然后,将数据存储在该可序列化的类型中,然后再进行序列化即可。
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("第一",1);
dic.Add("第二", 2);
dic.Add("第三", 3);
dic.Add("第四", 4);
List<DictionaryList> dicList = new List<DictionaryList>();
foreach (var a in dic)
{
DictionaryList dicl = new DictionaryList() { Name=a.Key, Value=a.Value};
dicList.Add(dicl);
}
//序列化
XmlSerializer ser = new XmlSerializer(typeof(List<DictionaryList>));
StringWriter writer = new StringWriter();
//序列化为Xml数据
ser.Serialize(writer, dicList);
MessageBox.Show(writer.ToString());
//反序列化
StringReader reader = new StringReader(writer.ToString());
List<DictionaryList> stuList2 = (List<DictionaryList>)ser.Deserialize(reader);
运行结果是:
5、图片序列化和反序列化
补充:XmlArray和XmlArrayItem的使用,用在数组中。
先构造实体类:
[XmlRoot("cats")]
public class CatCollection
{
[XmlArray("items"), XmlArrayItem("item")]
public Cat[] Cats { get; set; }
}
//[XmlRoot("cat")] 加不加都无所谓的。
public class Cat
{
//定义Color属性的序列化为cat节点的属性
[XmlAttribute("color")]
public string Color { get; set; }
//要求不序列化Speed属性
[XmlIgnore]
public int Speed { get; set; }
//设置Saying属性序列化为Xml子元素
[XmlElement("saying")]
public string Saying { get; set; }
}
现在,进行序列化:
//声明一个猫咪对象
var cWhite = new Cat { Color = "White", Speed = 10, Saying = "White or black, so long as the cat can catch mice, it is a good cat" };
var cBlack = new Cat { Color = "Black", Speed = 10, Saying = "White or black, so long as the cat can catch mice, it is a good cat" };
CatCollection cc = new CatCollection { Cats = new Cat[] { cWhite, cBlack } };
//序列化这个对象
XmlSerializer serializer = new XmlSerializer(typeof(CatCollection));
StringWriter writer = new StringWriter();
serializer.Serialize(writer,cc);
MessageBox.Show(writer.ToString());
运行结果是:
6、XmlSerializer内存泄漏问题
为了提高性能,XML 序列化基础结构将动态生成程序集,以序列化和反序列化指定类型。此基础结构将查找并重复使用这些程序集。此行为仅在使用以下构造函数时发生:
XmlSerializer(Type) XmlSerializer.XmlSerializer(Type, String)
如果使用任何其他构造函数,则会生成同一程序集的多个版本,且绝不会被卸载,这将导致内存泄漏和性能降低。最简单的解决方案是使用先前提到的两个构造函数的其中一个。
否则,必须在 Hashtable 中缓存程序集,如以下示例中所示。
/// <summary>
/// 提供xml文档序列化 反序列化
/// </summary>
public sealed class EncodeHelper
{
/// <summary>
/// 反序列化XML字符串为指定类型
/// </summary>
public static object Deserialize(string Xml, Type ThisType)
{
XmlSerializer xmlSerializer = new XmlSerializer(ThisType);
object result;
try
{
using (StringReader stringReader = new StringReader(Xml))
{
result = xmlSerializer.Deserialize(stringReader);
}
}
catch (Exception innerException)
{
bool flag = false;
if (Xml != null)
{
if (Xml.StartsWith(Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble())))
{
flag = true;
}
}
throw new ApplicationException(string.Format("Couldn't parse XML: '{0}'; Contains BOM: {1}; Type: {2}.",
Xml, flag, ThisType.FullName), innerException);
}
return result;
}
/// <summary>
/// 序列化object对象为XML字符串
/// </summary>
public static string Serialize(object ObjectToSerialize)
{
string result = null ;
try
{
XmlSerializer xmlSerializer = new XmlSerializer(ObjectToSerialize.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding(false));
xmlTextWriter.Formatting = Formatting.Indented;
xmlSerializer.Serialize(xmlTextWriter, ObjectToSerialize);
xmlTextWriter.Flush();
xmlTextWriter.Close();
UTF8Encoding uTF8Encoding = new UTF8Encoding(false, true);
result= uTF8Encoding.GetString(memoryStream.ToArray());
}
}
catch (Exception innerException)
{
throw new ApplicationException("Couldn't Serialize Object:" + ObjectToSerialize.GetType().Name, innerException);
}
return result;
}
}
也就是说我们在使用XmlSerializer序列化,初始化XmlSerializer对象时最好使用下面两个构造函数否则会引起内存泄漏。
XmlSerializer(Type) XmlSerializer.XmlSerializer(Type, String)
三、JSON 序列化和反序列化
1、JSON 简介
前面我们讨论了XML这种数据格式。XML的特点是功能全面,但标签繁琐,格式复杂。在Web上使用XML现在越来越少,取而代之的是JSON这种数据结构。
JSON是JavaScript Object Notation的缩写,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式。
一个典型的JSON如下:
{
"id": 1,
"name": "Java核心技术",
"author": {
"firstName": "Abc",
"lastName": "Xyz"
},
"isbn": "1234567",
"tags": ["Java", "Network"]
}
JSON作为数据传输的格式,有几个显著的优点:
- JSON只允许使用UTF-8编码,不存在编码问题;
- JSON只允许使用双引号作为key,特殊字符用
\
转义,格式简单; - 浏览器内置JSON支持,如果把数据用JSON发送给浏览器,可以用JavaScript直接处理。
因此,JSON适合表示层次结构,因为它格式简单,仅支持以下几种数据类型:
- 键值对:{"key": value}
- 数组:[1, 2, 3]
- 字符串:"abc"
- 数值(整数和浮点数):12.34
- 布尔值:true或false
- 空值:null
浏览器直接支持使用JavaScript对JSON进行读写:
// JSON string to JavaScript object:
jsObj = JSON.parse(jsonStr);
// JavaScript object to JSON string:
jsonStr = JSON.stringify(jsObj);
所以,开发Web应用的时候,使用JSON作为数据传输,在浏览器端非常方便。因为JSON天生适合JavaScript处理,所以,绝大多数REST API都选择JSON作为数据传输格式。
现在问题来了:使用Java如何对JSON进行读写?
2、Jackson 序列化与反序列化
1. Jackson简介
在Java中,针对JSON也有标准的JSR 353 API,但是我们在前面讲XML的时候发现,如果能直接在XML和JavaBean之间互相转换是最好的。类似的,如果能直接在JSON和JavaBean之间转换,那么用起来就简单多了。
常用的用于解析JSON的第三方库有:
- Jackson
- Gson
- Fastjson
- ...
注意到上一节提到的那个可以解析XML的浓眉大眼的Jackson也可以解析JSON,因此我们只需要引入以下Maven依赖:
- com.fasterxml.jackson.core:jackson-databind:2.12.0
就可以使用下面的代码解析一个JSON文件:
InputStream input = Main.class.getResourceAsStream("/book.json");
ObjectMapper mapper = new ObjectMapper();
// 反序列化时忽略不存在的JavaBean属性:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Book book = mapper.readValue(input, Book.class);
核心代码是创建一个ObjectMapper对象。
关闭DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES功能使得解析时如果JavaBean不存在该属性时解析不会报错。
2. 序列化
把JavaBean变为JSON,那就是序列化,如果把JSON解析为JavaBean的过程称为反序列化。
要实现JavaBean到JSON的序列化,只需要一行代码:
String json = mapper.writeValueAsString(book);
要把JSON的某些值解析为特定的Java对象,例如LocalDate,也是完全可以的。
例如:
{
"name": "Java核心技术",
"pubDate": "2016-09-01"
}
要解析为:
public class Book {
public String name;
public LocalDate pubDate;
}
只需要引入标准的JSR 310关于JavaTime的数据格式定义至Maven:
- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.0
然后,在创建ObjectMapper时,注册一个新的JavaTimeModule:
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
有些时候,内置的解析规则和扩展的解析规则如果都不满足我们的需求,还可以自定义解析。
举个例子,假设Book类的isbn是一个BigInteger:
public class Book {
public String name;
public BigInteger isbn;
}
但JSON数据并不是标准的整形格式:
{
"name": "Java核心技术",
"isbn": "978-7-111-54742-6"
}
直接解析,肯定报错。这时,我们需要自定义一个IsbnDeserializer,用于解析含有非数字的字符串:
public class IsbnDeserializer extends JsonDeserializer<BigInteger> {
public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// 读取原始的JSON字符串内容:
String s = p.getValueAsString();
if (s != null) {
try {
return new BigInteger(s.replace("-", ""));
} catch (NumberFormatException e) {
throw new JsonParseException(p, s, e);
}
}
return null;
}
}
然后,在Book类中使用注解标注:
public class Book {
public String name;
// 表示反序列化isbn时使用自定义的IsbnDeserializer:
@JsonDeserialize(using = IsbnDeserializer.class)
public BigInteger isbn;
}
类似的,自定义序列化时我们需要自定义一个IsbnSerializer,然后在Book类中标注@JsonSerialize(using = ...)即可。
3. 反序列化
在反序列化时,Jackson要求Java类需要一个默认的无参数构造方法,否则,无法直接实例化此类。存在带参数构造方法的类,如果要反序列化,注意再提供一个无参数构造方法。
对于enum字段,Jackson按String类型处理,即:
class Book {
public DayOfWeek start = MONDAY;
}
序列化为:
{
"start": "MONDAY"
}
对于record类型,Jackson会自动找出它的带参数构造方法,并根据JSON的key进行匹配,可直接反序列化。对record类型的支持需要版本2.12.0以上。
总结:
JSON是轻量级的数据表示方式,常用于Web应用;
Jackson可以实现JavaBean和JSON之间的转换;
可以通过Module扩展Jackson能处理的数据类型;
可以自定义JsonSerializer和JsonDeserializer来定制序列化和反序列化。
3、Fastjson 序列化与反序列化
1. Fastjson 简介
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。
Fastjson 特性:
- 提供服务器端、安卓客户端两种解析工具,性能表现较好。
- 提供了 toJSONString() 和 parseObject() 方法来将 Java 对象与 JSON 相互转换。调用toJSONString方 法即可将对象转换成 JSON 字符串,parseObject 方法则反过来将 JSON 字符串转换成对象。
- 允许转换预先存在的无法修改的对象(只有class、无源代码)。
- Java泛型的广泛支持。
- 允许对象的自定义表示、允许自定义序列化类。
- 支持任意复杂对象(具有深厚的继承层次和广泛使用的泛型类型)。
下载和使用:
你可以在 maven 中央仓库中直接下载,或者配置 Maven 依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>x.x.x</version> <!-- 根据需要使用特定版本,建议使用最新版本 -->
</dependency>
2. 序列化:toJSONString()
1)序列化对象
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class User {
/**
* @JSONField 作用:自定义对象属性所对应的 JSON 键名
* @JSONField 的作用对象:
* 1. Field
* 2. Setter 和 Getter 方法
* 注意:
* 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
* 2. 若没有 @JSONField 注解,则直接使用属性名。
*/
@JSONField(name="NAME")
private String name;
@JSONField(name="AGE")
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class ObjectTest {
private static List<User> userList = new ArrayList<User>();
@BeforeAll
public static void setUp() {
userList.add(new User("xiaoming", 18));
userList.add(new User("xiaodan", 19));
}
@DisplayName("序列化对象")
@Test
public void testObjectToJson() {
String userJson = JSON.toJSONString(userList.get(0));
System.out.println(userJson); // {"AGE":18,"NAME":"xiaoming"}
}
@DisplayName("序列化集合")
@Test
public void testListToJson() {
String userListJson = JSON.toJSONString(userList);
System.out.println(userListJson); // [{"AGE":18,"NAME":"xiaoming"},{"AGE":19,"NAME":"xiaodan"}]
}
@DisplayName("序列化数组")
@Test
public void testArrayToJson() {
User[] userArray = new User[5];
userArray[0] = new User("zhangsan", 20);
userArray[1] = new User("lisi", 21);
String userArrayJson = JSON.toJSONString(userArray);
System.out.println(userArrayJson); // [{"AGE":20,"NAME":"zhangsan"},{"AGE":21,"NAME":"lisi"},null,null,null]
}
@DisplayName("序列化映射")
@Test
public void testMapToJson() {
Map<Integer, User> userMap = new HashMap<Integer, User>();
userMap.put(1, new User("xiaotie", 10));
userMap.put(2, new User("xiaoliu", 11));
String userMapJson = JSON.toJSONString(userMap);
System.out.println(userMapJson); // {1:{"AGE":10,"NAME":"xiaotie"},2:{"AGE":11,"NAME":"xiaoliu"}}
}
}
2)序列化指定属性字段
利用 JSON.toJSONString 方法序列化指定属性字段,主要通过设置属性预过滤器(SimplePropertyPreFilter)的包含属性字段列表(includes)实现。
主要应用于只想验证某些字段的情况,比如只验证跟测试用例有关的字段。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.*;
class User {
/**
* @JSONField 作用:自定义对象属性所对应的 JSON 键名
* @JSONField 的作用对象:
* 1. Field
* 2. Setter 和 Getter 方法
* 注意:
* 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
* 2. 若没有 @JSONField 注解,则直接使用属性名。
*/
@JSONField(name="NAME")
private String name;
@JSONField(name="AGE")
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class ObjectTest {
@DisplayName("指定所有类的属性字段")
@Test
public void testAllClassField() {
User user = new User("xiaoming", 18);
SimplePropertyPreFilter filter = new SimplePropertyPreFilter(); // 默认所有类型的类均可转换
filter.getIncludes().addAll(Arrays.asList("NAME", "AGE")); // 需存在于 @JSONField
String text = JSON.toJSONString(user, filter);
System.out.println(text); // {"AGE":18,"NAME":"xiaoming"}
}
@DisplayName("指定单个类的个别属性字段")
@Test
public void testOneClassField() {
ArrayList<User> users = new ArrayList<>();
users.add(new User("xiaodan", 18));
users.add(new User("xiaoxue", 19));
SimplePropertyPreFilter filter = new SimplePropertyPreFilter(User.class); // 指定User类
filter.getIncludes().addAll(Arrays.asList("NAME"));
String text = JSON.toJSONString(users, filter);
System.out.println(text); // [{"NAME":"xiaodan"},{"NAME":"xiaoxue"}]
}
}
3)序列化排除属性字段
利用 JSON.toJSONString 方法序列化过滤属性字段,主要通过设置属性预过滤器(SimplePropertyPreFilter)的排除属性字段列表(excludes)实现。
主要应用于不想验证某些字段的情况,比如排除无法验证的随机属性字段。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.*;
class User {
/**
* @JSONField 作用:自定义对象属性所对应的 JSON 键名
* @JSONField 的作用对象:
* 1. Field
* 2. Setter 和 Getter 方法
* 注意:
* 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
* 2. 若没有 @JSONField 注解,则直接使用属性名。
*/
@JSONField(name="NAME")
private String name;
@JSONField(name="AGE")
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class ObjectTest {
@DisplayName("排除所有类的属性字段")
@Test
public void testAllClassField() {
User user = new User("xiaoming", 18);
SimplePropertyPreFilter filter = new SimplePropertyPreFilter(); // 默认所有类型的类均可转换
filter.getExcludes().addAll(Arrays.asList("NAME")); // 排除 NAME 字段(需存在于 @JSONField)
String text = JSON.toJSONString(user, filter);
System.out.println(text); // {"AGE":18}
}
@DisplayName("排除指定类的属性字段")
@Test
public void testOneClassField() {
ArrayList<User> users = new ArrayList<>();
users.add(new User("xiaodan", 18));
users.add(new User("xiaoxue", 19));
SimplePropertyPreFilter filter = new SimplePropertyPreFilter(User.class); // 指定User类
filter.getExcludes().addAll(Arrays.asList("AGE"));
String text = JSON.toJSONString(users, filter);
System.out.println(text); // [{"AGE":18},{"AGE":19}]
}
}
3. 反序列化:parseObject() / parseArray()
1)反序列化对象
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.annotation.JSONField;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.*;
class User {
/**
* @JSONField 作用:自定义对象属性所对应的 JSON 键名
* @JSONField 的作用对象:
* 1. Field
* 2. Setter 和 Getter 方法
* 注意:
* 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
* 2. 若没有 @JSONField 注解,则直接使用属性名。
*/
// @JSONField(name="name")
private String name;
// @JSONField(name="age")
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class ObjectTest {
@DisplayName("反序列化对象")
@Test
public void testJsonToObject() {
String text = "{\"age\":18,\"name\":\"xiaoming\"}";
User user = JSON.parseObject(text, User.class);
System.out.println(user); // User{name='xiaoming', age=18}
}
@DisplayName("反序列化数组")
@Test
public void testJsonToArray() {
String text = "[{\"age\":18,\"name\":\"xiaoming\"}, {\"age\":19,\"name\":\"xiaowa\"}]";
User[] users = JSON.parseObject(text, User[].class);
System.out.println(Arrays.toString(users)); // [User{name='xiaoming', age=18}, User{name='xiaowa', age=19}]
}
@DisplayName("反序列化集合")
@Test
public void testJsonToCollection() {
String text = "[{\"age\":18,\"name\":\"xiaoming\"}, {\"age\":19,\"name\":\"xiaowa\"}]";
// List 集合
List<User> userList = JSON.parseArray(text, User.class);
System.out.println(Arrays.toString(userList.toArray())); // [User{name='xiaoming', age=18}, User{name='xiaowa', age=19}]
// Set 集合
Set<User> userSet = JSON.parseObject(text, new TypeReference<Set<User>>() {});
System.out.println(Arrays.toString(userSet.toArray())); // [User{name='xiaowa', age=19}, User{name='xiaoming', age=18}]
}
@DisplayName("反序列化映射")
@Test
public void testJsonToMap() {
String text = "{1:{\"age\":18,\"name\":\"xiaoming\"}, 2:{\"age\":19,\"name\":\"xiaowa\"}}";
Map<Integer, User> userList = JSON.parseObject(text, new TypeReference<Map<Integer, User>>() {});
for (Integer i : userList.keySet()) {
System.out.println(userList.get(i));
}
/*
User{name='xiaoming', age=18}
User{name='xiaowa', age=19}
*/
}
}
2)反序列化非公有字段
由于某些属性字段没有公有设置方法,或者没有以字段名称作为公有设置方法,那么当需要反序列化这些属性字段时,需要指定 SupportNonPublicField(支持非公有字段)反序列化参数。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.*;
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
private int getAge() {
return age;
}
private void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class PrivateTest {
@DisplayName("反序列化非公有字段")
@Test
public void testJsonToObject() {
String text = "{\"age\":18,\"name\":\"xiaoming\"}";
Person person = JSON.parseObject(text, Person.class, Feature.SupportNonPublicField);
System.out.println(person.toString()); // Person{name='xiaoming', age=18}
}
}
四、YAML 序列化和反序列化
1、YAML简介
- YAML(YAML Ain't Markup Language,即 YAML 不是一种标记语言),也可以叫做 YML 。YAML 是一种直观的、能够被电脑识别的数据序列化格式,容易被人类阅读,容易和脚本语言交互,可以被支持 YAML 库的不同编程语言程序所导入(如 C/C++、Ruby、Python、Java、Perl、C#、PHP 等)。
- YML 文件是以数据为核心的,相比 JSON、XML 等方式更加简洁。
- YAML 文件的扩展名可以使用 .yml 或者 .yaml 。
YAML 语法:
- 大小写敏感。
- 数据值前边必须要有空格(大于等于 1 个)作为分隔符。
- 使用缩进表示层级关系。
- 缩进时不允许使用 Tab 键,只允许使用空格(各个系统 Tab对应的 空格数目可能不同,导致层次混乱)。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。
- # 表示注释,从这个字符一直到行尾,都会被解析器忽略。
YAML 数据格式:
- 对象(map):键值对的集合
# 行内写法
address: {province: 山东, city: 济南}
# 多行写法
address:
province: 山东
city: 济南
- 数组:一组按次序排列的值
# 行内写法
hobbyList: [游泳, 跑步]
# 多行写法
hobbyList:
- 游泳
- 跑步
- 纯量:单个的、不可再分的值
# 字符串默认不用加引号,但包含空格或特殊字符必须加引号,单引号或双引号都可以
# 单引号:不识别转移字符,即原样输出
# 双引号:识别转移字符,如 \r、\n 等
userId: S123
username: "lisi"
password: '123456'
province: 山东
city: "济南 : ss"
# 布尔值
success: true
# 整数
age: 13
# 浮点数
weight: 75.5
# Null
gender: ~
# 时间:使用 ISO8601 标准
createDate: 2001-12-14T21:59:43.10+05
- 参数引用
name: lisi
person:
name: ${name} # 引用上边定义的name值
2、YAML 序列化和反序列化
1. yaml文件与Bean类
示例:yaml 文件。
userId: 1
username: lisi
password: 123456
address: {province: 山东, city: 济南}
hobbyList: [游泳, 跑步]
或:
userId: 1
username: "lisi"
password: '123456'
address:
province: 山东
city: "济南 : ss"
hobbyList:
- 游泳
- 跑步
示例:Bean 实体类。
- Maven 依赖:
<!-- Bean类的GETTER、SETTER注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
- User 实体类:
import lombok.*;
import java.security.Timestamp;
import java.util.List;
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String userId;
private String username;
private String password;
private Timestamp createDate;
private Address address;
private List<String> hobbyList;
}
- Address 实体类:
import lombok.*;
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String province;
private String city;
}
2. snakeyaml 库
Maven 依赖:
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.21</version>
</dependency>
1)yaml、map 互转
使用 yaml 对象中的 load 方法会返回一个 map 对象,然后遍历这个 map 即可得到自己想要的数据。
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
public class YamlDemo {
public static void main(String[] args) {
// yaml 读取
InputStream in = YamlDemo.class.getClassLoader().getResourceAsStream("test.yaml");
Yaml yaml = new Yaml();
Map<String, Object> map = yaml.loadAs(in, Map.class);
map.forEach(
(String key, Object value) -> {
System.out.println("key: "+key+" value: "+value);
}
);
/* 执行结果:
key: userId value: 1
key: username value: lisi
key: password value: 123456
key: address value: {province=山东, city=济南}
key: hobbyList value: [游泳, 跑步]
*/
// yaml 写入
map.put("username", "zhangsan"); // 修改读取的yaml内容
try {
// 将修改后的内容写入new_user.yaml
yaml.dump(map, new OutputStreamWriter(new FileOutputStream(new File("new_user.yaml"))));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
执行结果:
key: userId value: 1
key: username value: lisi
key: password value: 123456
key: address value: {province=山东, city=济南}
key: hobbyList value: [游泳, 跑步]
2)yaml 转 Bean
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.util.Objects;
public class YamlDemo {
public static void main(String[] args) {
InputStream resource = YamlDemo.class.getClassLoader().getResourceAsStream("test.yaml");
if (Objects.nonNull(resource)) {
Yaml yaml = new Yaml();
User user = yaml.loadAs(resource, User.class);
System.out.println(user.getClass()); // class User
System.out.println(user); // User(userId=1, username=lisi, password=123456, createDate=null, address=Address(province=山东, city=济南), hobbyList=[游泳, 跑步])
}
}
}
3)Bean 转 yaml
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.util.Arrays;
public class YamlDemo {
public static void main(String[] args) {
User user = new User();
user.setUserId("1");
user.setUsername("lisi");
user.setPassword("123456");
user.setAddress(new Address("山东", "济南"));
user.setHobbyList(Arrays.asList("游泳", "跑步"));
Yaml yaml = new Yaml();
String userString = yaml.dump(user); // 输出字符串
try {
yaml.dump(user, new FileWriter("Bean.yaml")); // 输出文件
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(userString);
System.out.println(yaml.loadAs(userString, User.class));
}
}
输出结果:
!!User # 首行为:!!+全类名
address: {city: 济南, province: 山东}
createDate: null
hobbyList: [游泳, 跑步]
password: '123456'
userId: '1'
username: lisi
上面的对象和数组是显示在一行的,我们也可以通过自定义序列化显示为多行。
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.util.Arrays;
public class YamlDemo {
public static void main(String[] args) {
User user = new User();
user.setUserId("1");
user.setUsername("lisi");
user.setPassword("123456");
user.setAddress(new Address("山东", "济南"));
user.setHobbyList(Arrays.asList("游泳", "跑步"));
DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(dumperOptions);
String userString = yaml.dump(user); // 输出字符串
try {
yaml.dump(user, new FileWriter("Bean.yaml")); // 输出文件
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(userString);
System.out.println(yaml.loadAs(userString, User.class));
}
}
执行结果:
!!User
address:
city: 济南
province: 山东
createDate: null
hobbyList:
- 游泳
- 跑步
password: '123456'
userId: '1'
username: lisi
3. jackson 库
jackson-dataformat-yaml 是在 snakeyaml 的基础上又封装了一层。
Maven 依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.12.0</version>
</dependency>
1)yaml 转 Bean
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
public class YamlDemo {
public static void main(String[] args) {
InputStream resource = YamlDemo.class.getClassLoader().getResourceAsStream("test.yaml");
if (Objects.nonNull(resource)) {
YAMLMapper yamlMapper = new YAMLMapper();
User user = null;
try {
user = yamlMapper.readValue(resource, User.class);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(user.getClass()); // class User
System.out.println(user); // User(userId=1, username=lisi, password=123456, createDate=null, address=Address(province=山东, city=济南), hobbyList=[游泳, 跑步])
}
}
}
2)Bean 转 yaml
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import java.io.FileWriter;
import java.util.Arrays;
public class YamlDemo {
public static void main(String[] args) {
User user = new User();
user.setUserId("1");
user.setUsername("lisi");
user.setPassword("123456");
user.setAddress(new Address("山东", "济南"));
user.setHobbyList(Arrays.asList("游泳", "跑步"));
YAMLMapper yamlMapper = new YAMLMapper();
try {
System.out.println(yamlMapper.writeValueAsString(user));
yamlMapper.writeValue(new FileWriter("Bean.yaml"), user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果:
---
userId: "1"
username: "lisi"
password: "123456"
createDate: null
address:
province: "山东"
city: "济南"
hobbyList:
- "游泳"
- "跑步"
标签:XML,Java,name,JSON,详解,new,序列化,public
From: https://blog.51cto.com/u_11837698/6165247