XML和JSON
XML
参考博客:https://blog.csdn.net/gavin_john/article/details/51511180
xml(eXtensible Markup Language)可扩展标记语言,它具有平台无关性,是一门独立的标记语言。不论是Pyhton、Java还是C语言,都可以解析xml的数据或者生成xml,这一特性是xml可以作为程序之间通信介质的重要前提。
xml可以以多种形式保存,.xml文件只是其中的一种,我们还可以在内存中构建xml数据。
发展历史:
- 1969 gml(通用标记语言),主要目的是要在不同的机器之间进行通信的数据规范
- 1985 sgml(标准通用标记语言)
- 1993 html(超文本标记语言,www网)
- 1998 xml extensiable markup language 可扩展标记语言
XML的作用
- 用于两个程序之间的数据传输,比如Java中类的数据可以构造成xml数据进行传输
- 给服务器作配置文件,帮助其获取监听的端口号、数据库用户名及密码等
- 数据存储,甚至可以充当小型的数据库(比如msn中保存用户聊天记录就是用XML文件)
XML语法
下面是一个xml格式的一个简单例子:
<?xml version="1.0" encoding="UTF-8"?>
<class>
<stu id="001">
<name>张三</name>
<gender>男</gender>
<age>18</age>
</stu>
<stu id="002">
<name>小红</name>
<gender>女</gender>
<age>16</age>
</stu>
</class>
一个xml文件包含以下几部分内容:
- 文档声明
- 命名空间(可选)
- 元素
- (元素的)属性
- 注释
- CDATA区、特殊字符
- 处理指令(processing instruction)
文档声明
<?xml version="1.0" encoding="UTF-8"?>
包含version(文档符合的xml版本号),encoding(文档的字符编码)。
元素(标签/节点/标记)
<元素名>标签体</元素名> <!--包含标签体-->
</元素名> <!--不含标签体-->
- 每个xml文档有且只有一个根元素
- 一个元素可以嵌套任意个子元素,但是不允许元素交叉嵌套
- xml标签体中的空格和换行都会被当作标签内容,以下两个标签的含义是不一样的
<stu>小明</stu>
<stu>
小明
</stu>
-
元素命名规范:
- 名称可以含字母、数字以及其他的字符 - 大小写敏感,例如,元素P和元素p是两个不同的元素 - 不能以数字或下划线”_”开头 - 元素内不能包含空格 - 名称中间不能包含冒号(:) - 可以使用中文,但一般不这么用 - 名称不能以字符"xml"(或者XML、Xml)开始
属性
<元素名 属性名1="属性值1" 属性名2="属性值2"></元素名>
<元素名>
<属性名1 value="属性值1"/>
<属性名2 value="属性值2"/>
</元素名>
属性值用双引号(”)或单引号(’)分隔,如果属性值中有单引号,则用双引号分隔;如果有双引号,则用单引号分隔。那么如果属性值中既有单引号还有双引号怎么办?这种要使用实体(转义字符,类似于html中的空格符),XML有5个预定义的实体字符,如下:
实体字符 | 代表字符 | 含义 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
& | & | 和 |
' | ' | 单引号 |
" | " | 双引号 |
CDATA节
有些内容可能不想让解析引擎解析执行,而是当做原始内容处理,用于把整段文本解释为纯字符数据而不是标记。这就要用到CDATA节
<stu id="001">
<name>张三</name>
<gender>男</gender>
<age>18</age>
<intro><![CDATA[ad<<&$^#*k]]></intro>
</stu>
处理指令
在同一文件夹目录中创建以下两个文件:
my.css:
name{
font-size:80px;
font-weight:bold;
color:red;
}
gender{
font-size:60px;
font-weight:bold;
color:blue;
}
age{
font-size:40px;
font-weight:bold;
color:green;
}
my.xml(在xml中引用my.css):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="my.css" type="text/css"?>
<class>
<stu id="001">
<name>张三</name>
<gender>男</gender>
<age>18</age>
</stu>
<stu id="002">
<name>小红</name>
<gender>女</gender>
<age>16</age>
</stu>
</class>
然后使用浏览器打开,发现已经套上了css的样式(如果发现乱码请改变xml文件的字符集):
PS:如果出现乱码会是这样的
命名空间
<!--命名空间-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper SYSTEM "http://mybatis.org/dtd/mybatis-3-mapper.dtd" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN">
<mapper namespace="com.sketch.vueblog.mapper.MBlogMapper"> </mapper>
mybatis的mapper、Spring的配置文件、maven的pom文件都需要在文件头写入命名空间,它可以提供一定的标签语法检查(这样有时候就不能自定义标签了)
Java解析和生成XML文件
导入dom4j包(同时还要导入jaxen包,因为dom4j有很多东西是依赖jaxen的)
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
从本地文件读取xml
students.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="my.css" type="text/css"?>
<class>
<stu id="001" gender="男">
<rank value="2"/>
<name>张三</name>
<age>18</age>
</stu>
<stu id="002" gender="女">
<rank value="1"/>
<name>小红</name>
<age>16</age>
</stu>
</class>
private Element getRootElementFromFile(String filePath) throws FileNotFoundException, DocumentException {
// 创建一个指向XML文件的输入流
FileInputStream fis = new FileInputStream(filePath);
// 创建一个XML读取工具对象
SAXReader sr = new SAXReader();
// DOMReader dr = new DOMReader(); // 也可以创建DOMReader(区别有待探究)
Document doc = sr.read(fis);
// 获取根节点
Element root = doc.getRootElement();
return root;
}
private void test1() {
try {
Element root = getRootElementFromFile("students.xml");
// 获取根节点名称
String rootName = root.getName();
System.out.println(rootName);
// 获取根节点列表
List<Element> elements = root.elements();
for (Element element : elements) {
// 获取根节点属性
String id = element.attributeValue("id");
String gender = element.attributeValue("gender");
// 获取子节点内容
String name = element.element("name").getText();
String age = element.element("age").getText();
System.out.println(name + "-" + id + "-" + gender + "-" + age);
}
} catch (FileNotFoundException | DocumentException e) {
e.printStackTrace();
}
}
从url资源读取xml
private Element getRootElementFromURL(String urlStr) throws IOException, DocumentException {
// 获取XML资源输入流
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
SAXReader sr = new SAXReader();
Document doc = sr.read(is);
// Document doc = sr.read(url);
Element root = doc.getRootElement();
return root;
}
private void test2() {
try {
String phone = "17727478831";
Element root = getRootElementFromURL("http://apis.juhe.cn/mobile/get?phone="+ phone + "&dtype=xml&key=9f3923e8f87f1ea50ed4ec8c39cc9253");
String code = root.elementText("resultcode");
Node node = root.selectSingleNode("//company");
System.out.println(node.getName() + ":" + node.getText());
if ("200".equals(code)) {
Element result = root.element("result");
String province = result.elementText("province");
String city = result.elementText("city");
if (province.equals(city)) {
System.out.println("手机号码归属地为:" + city);
} else {
System.out.println("手机号码归属地为:" + province + city);
}
} else {
System.out.println("请输入正确的手机号码");
}
// System.out.println("手机运营商为:" + node.getText());
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
Xpath语法
我们注意到上述代码中有一个方法
Node node = root.selectSingleNode("//company");
该方法可以在xml中层序遍历找到company标签,这里的"//company"
就是Xpath语法
其用法详细总结可以参考:https://blog.csdn.net/u013332124/article/details/80621638
基本语法:
表达式 描述 nodename 选取此节点的所有子节点。 / 从根节点选取。 // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 . 选取当前节点。 … 选取当前节点的父节点。 @ 选取属性。 示例:
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()❤️] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
XML的四种解析方式(了解即可)
- SAX解析:逐行解析,不占空间,但不能随时回溯
- DOM解析:”通读全文(整个xml全部读到内存)“,较占空间,但因为xml文件一般比较小也没有关系,可以随时访问任意一行的元素
- JDOM解析
- DOM4J解析
JSON
JSON官方文档:http://www.json.org/json-zh.html
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。
JSON建构于两种结构:
- “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
- 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。
这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。
JSON的作用
最常用于前后端数据交互,比如说后端将一个对象传到前端可以将对象转成JSON字符串再传输
JSON语法
JSON 的语法规则十分简单,可称得上“优雅完美”,总结起来有:
数组(Array)用方括号(“[]”)表示。
对象(Object)用大括号(”{}”)表示。
名称/值对(name/value)组合成数组和对象。
名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
并列的数据之间用逗号(“,”)分隔
其中对象和数组还可以互相嵌套
// 正确的json格式(PS:如果是字符串,不论是键还是值最好都用双引号括起来,否则有可能不能通过校验)
{
"password": 123456,
"name": "myname",
"Booleans": true,
"Array": [{"name": "cxd"},"y", "z"],
"object": {}
}
json字符串还可以压缩、转义,这样更加方便以字符串形式传输
{\"password\":123456,\"name\":\"myname\",\"Booleans\":true,\"Array\":[{\"name\":\"cxd\"},\"y\",\"z\"],\"object\":{}}
Java解析和生成JSON
导入fastjson包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
json的生成和解析都超级超级方便
在fastjson中,点开JSON这个类的介绍我们可以看到
This is the main class for using Fastjson. You usually call these two methods toJSONString(Object) and parseObject(String, Class).
Here is an example of how fastjson is used for a simple Class:
Model model = new Model();
String json = JSON.toJSONString(model); // serializes model to Json
Model model2 = JSON.parseObject(json, Model.class); // deserializes json into model2
这便是fastjson最核心也是最常用的用法,首先我们编写以下测试用的Java类
请注意!!!对象的属性必须设为public或必须居有get和set方法,否则不能正常解析!
// 构造方法、get/set方法和toString方法省略了(均为内部类)
class Student {
String name;
int age;
List<Subject> subjects;
}
class Subject {
String name;
int score;
}
// 将对象转为json字符串
List<Subject> subjects = Arrays.asList(
new Subject("语文", 88),
new Subject("英语", 87));
Student stu = new Student("cxd", 18, subjects);
String jsonStr = JSON.toJSONString(stu);
System.out.println(jsonStr);
运行结果:
{"age":18,"name":"cxd","subjects":[{"name":"语文","score":88},{"name":"英语","score":87}]}
让我们试着再把这个字符串转回Java对象,遇到如下问题↓
问题一:fastjson将字符串转换为类时抛出ArrayIndexOutOfBoundsException
String jsonStr = "{\"age\":18,\"name\":\"cxd\",\"subjects\":[{\"name\":\"语文\",\"score\":88},{\"name\":\"英语\",\"score\":87}]}";
JSONObject jsonObj = JSON.parseObject(jsonStr);
Student stu = jsonObj.toJavaObject(Student.class);
原因未知,作出以下尝试时又遇到问题二↓
问题二:不能转换非静态内部类
我怀疑可能是对象Student中的List又嵌套了一个自定义类Subject,导致类型转换什么的出错,所以我又重新写了两个类
class Student2 {
public String name;
public int age;
public HomeTown ht;
}
class HomeTown {
public String name;
}
// 测试代码
Student2 stu = new Student2();
HomeTown ht = new HomeTown();
ht.name = "广东";
stu.age = 18; stu.name = "cxd"; stu.ht = ht;
String jsonStr = JSON.toJSONString(stu);
Student2 stu2 = JSON.parseObject(jsonStr, Student2.class);
System.out.println(stu2.name + stu2.age + stu2.ht.name);
抛出如下错误,大概意思是不能创建非静态的内部类,于是在Student2和HomeTown两个类前添加static修饰,问题解决
经过以上尝试后,我在Student和Subject类前也加了static修饰符,但又抛出了如下异常
Exception in thread “main” com.alibaba.fastjson.JSONException: create instance error, public com.cxd.json.JSONTest S t u d e n t ( j a v a . l a n g . S t r i n g , i n t , j a v a . u t i l . L i s t < c o m . c x d . j s o n . J S O N T e s t Student(java.lang.String,int,java.util.List<com.cxd.json.JSONTest Student(java.lang.String,int,java.util.List<com.cxd.json.JSONTestSubject>)
at com.alibaba.fastjson.util.TypeUtils.castToJavaBean(TypeUtils.java:1551)
at com.alibaba.fastjson.JSONObject.toJavaObject(JSONObject.java:614)
at com.cxd.json.JSONTest.test2(JSONTest.java:115)
at com.cxd.json.JSONTest.main(JSONTest.java:13)
Caused by: com.alibaba.fastjson.JSONException: create instance error, public com.cxd.json.JSONTest S t u d e n t ( j a v a . l a n g . S t r i n g , i n t , j a v a . u t i l . L i s t < c o m . c x d . j s o n . J S O N T e s t Student(java.lang.String,int,java.util.List<com.cxd.json.JSONTest Student(java.lang.String,int,java.util.List<com.cxd.json.JSONTestSubject>)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.createInstance(JavaBeanDeserializer.java:1585)
at com.alibaba.fastjson.util.TypeUtils.castToJavaBean(TypeUtils.java:1549)
… 3 more
Caused by: java.lang.IllegalAccessException: Class com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer can not access a member of class com.cxd.json.JSONTest$Student with modifiers “public”
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Constructor.newInstance(Constructor.java:413)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.createInstance(JavaBeanDeserializer.java:1582)
… 4 more
最后我将两个内部类迁移独立出来,重新编写成Student.java和Subject.java,问题解决~
小总结
- JSON将可以将普通内部类转化为JSON字符串,但如果要将JSON字符串转回内部类,该内部类必须是静态的
- 如果两个静态内部类有以集合形式的互相嵌套,也不能够成功解析(普通的嵌套可以,比如Student2和HomeTown)
- 尽量不要出现上述这样嵌套的情况,一般来说JSON的作用是前后端交互,所以传输的对象一般就是携带信息的实体类,里面只会放一些简单的String、int、boolean等这些常用属性。如果传输的对象过重过于复杂,非常容易出问题。
JSONObject和JSONArray
JSONObject和JSONArray是JSON的两个子类
下面是我写过的在一个长JSON串中递归寻找一个JSONArray(名称为"subjects")的代码
private JSONArray getSubjects(JSONObject jObj) {
if (jObj.entrySet() == null || jObj.entrySet().size() == 0) {
return null;
}
for (Map.Entry<String, Object> jsonEntry : jObj.entrySet()) {
if (jsonEntry.getValue() instanceof JSONArray && jsonEntry.getKey().equals("subjects")) {
return (JSONArray) jsonEntry.getValue();
} else if (jsonEntry.getValue() instanceof JSONObject){
JSONArray temp = null;
if ((temp = getSubjects((JSONObject) jsonEntry.getValue())) != null) {
return temp;
}
}
}
return null;
}
其实我们就可以把JSON理解成只有JSONObject(ObjectMap,键值对)和JSONArray(ObjectArray,对象数组)两种数据形式。JSONObject继承了Map<String, Object>,JSONArray继承了List< Object >,所以他们分别会具有Map和List的默认方法,而JSON字符串的数据被转换成JSON后,也就是以Map和List的形式储存了起来。
XML和JSON的对比
- xml的标签可以重复,但json的键值对的键不能重复
- xml标签中的空格和换行号也会被解析,json键值对之外的空格和换行号不会被解析
- json不需要规定版本号和字符编码,其表达形式也更加简洁
- xml可以引入命名空间,从而限制标签的类型;而json没有命名空间,我理解json更像是一个ObjectMap
- json可以直接以字符串形式表示,不一定需要存储到.json文件中
- json传输的内容相对来说会更轻量