本文讲述如何创建一个JSON结构字符串。我们有三种方式创建JSON:
- Java - 最常用的一种方式是将Java对象序列化成JSON。
- Tree representation - 我们先创建一个
com.fasterxml.jackson.databind.JsonNode
(Jackson 2.x版本用这个包)对象树,然后将这棵树转换成JSON。 - Stream generator - 这种方法我们要用
Stream generator
构建一个json流然后将其转换成一个JSON结构。
Java对象转JSON
Jackson提供了Java对象序列化成JSON和JSON反序列化成Java对象的类。本示例将演示将Java对象序列化成JSON结构。我们将从一个简单的类开始,然后逐步增加复杂性。假设我们有一个音乐公司,想发布一个API供用户来查询唱片集。我们先建一个只有title
属性的Album
类。
1 class Album {
2 private String title;
3
4 public Album(String title) {
5 this.title = title;
6 }
7
8 public String getTitle() {
9 return title;
10 }
11 }
我们用ObjectMapper
类将对象转换成JSON。默认情况下,Jackson会用BeanSerializer
类序列化POJO(简单Java对象)。
> 记住Java Bean对象中private
修饰的属性要有getter
方法,或者该属性被public
修饰,可以没有getter
方法。如果对象中只有private
的属性且没有对应的getter
方法,Jackson将报异常,但是如果对象中既有private
、public
修饰的属性,那么private
属性没有getter
方法也不会报异常,但是如果没有其他Jackson注解,该属性序列化时将不可见,也即JSON中将没有该字段。
转换的JSON结构如下:
{"title":"Kind Of Blue"}
让我们给这个唱片集增加一组链接,当然还有相应的getter
、setter
方法,方便链接到唱片发布会或是评论。
1 private String[] links;
在main
方法中增加如下语句:
1 album.setLinks(new String[] { "link1", "link2" });
现在的JSON结构如下,注意Java Array
被转换成被[
、]
包围JSON数组。
1 {"title":"Kind Of Blue","links":["link1","link2"]}
现在我们给Album增加一个歌曲List
.
1 List<String> Songs = new ArrayList<String>();
在main
方法中给Album
对象增加歌曲List
.
1 List<String> songs = new ArrayList<String>();
2 songs.add("So What");
3 songs.add("Flamenco Sketches");
4 songs.add("Freddie Freeloader");
5 album.setSongs(songs);
转换的结构如下,注意List
对象和上面的Array
对象一样也被转换成json 数组。
1 {"title":"Kind Of Blue","links":["link1","link2"],
2 "songs":["So What","Flamenco Sketches","Freddie Freeloader"]}
接下来,我们将给唱片集增加一个艺术家属性。艺术家类是一个包含姓名和Date
实例表示的出生日期的类。
1 class Artist {
2 public String name;
3 public Date birthDate;
4 }
在main
方法中给唱片集增加一个艺术家。
1 Artist artist = new Artist();
2 artist.name = "Miles Davis";
3 SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
4 artist.birthDate = format.parse("26-05-1926");
5 album.setArtist(artist);
Json结构如下:
1 {"title":"Kind Of Blue","links":["link1","link2"],
2 "songs":["So What","Flamenco Sketches","Freddie Freeloader"],
3 "artist":{"name":"Miles Davis","birthDate":-1376027600000}}
为了使Jackson转换出来的JSON字符串更具可读性,我们增加了一行代码。
1 mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
格式化后的JSON结构如下:
1 {
2 "title" : "Kind Of Blue",
3 "links" : [ "link1" , "link2" ],
4 "songs" : [ "So What", "Flamenco Sketches", "Freddie Freeloader" ],
5 "artist" : {
6 "name" : "Miles Davis",
7 "birthDate" : -1376027600000
8 }
9 }
注意这个不应该用于实际发布的时候,仅仅用于开发和测试阶段,因为为了达到换行和缩进的效果,会在每行的头尾部增加
\r\n
、\t
等字符,这会显著增加数据传输量。
现在我们再在唱片集中添加一个作曲家和他们所使用的乐曲之间的Map。
1 private Map<String,String> musicians = new HashMap<String, String>();
2 public Map<String, String> getMusicians() {
3 return Collections.unmodifiableMap(musicians);
4 }
5 public void addMusician(String key, String value){
6 musicians.put(key, value);
7 }
在main
方法中添加:
1 album.addMusician("Miles Davis", "Trumpet, Band leader");
2 album.addMusician("Julian Adderley", "Alto Saxophone");
3 album.addMusician("Paul Chambers", "double bass");
json结构如下:
1 {
2 "title" : "Kind Of Blue",
3 "links" : [ "link1", "link2" ],
4 "songs" : [ "So What", "Flamenco Sketches", "Freddie Freeloader" ],
5 "artist" : {
6 "name" : "Miles Davis",
7 "birthDate" : -1376027600000
8 },
9 "musicians" : {
10 "Julian Adderley" : "Alto Saxophone",
11 "Paul Chambers" : "double bass",
12 "Miles Davis" : "Trumpet, Band leader"
13 }
14 }
我们还可以给这个转换过程添加更多的特性,先告诉mapper按照Map中keys的自然顺序排序:
1 mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
这里有一个问题,细心的读者可能已经发现birthDate
字段已经被格式化成新纪元时间,这不是我们想要到,因此还应当将时间格式化为人类更可读的形式。
1 SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMM yyyy");
2 mapper.setDateFormat(outputFormat);
默认情况下,Jackson会使用Java成员属性名作为Json字段名。你可以像本教程一样用Jackson Annotations来改变默认的命名策略。然而有时你可能无法直接访问Java Bean对象,比如那是一个第三方库,你无法在其类的源码上添加Annotations,又或者你不想Java bean和Jackson Annotation混在一起。对于这些情况,Jackson也提供了一种优雅的方法来改变默认的字段命名策略。可以在mapper
上使用setPropertyNamingStrategy
方法来为field
设置命名策略。如果在bean
中有public
域,可以重写nameForField
方法;如果bean
中有getter
方法,那么可以重写nameForGetter
方法。在下面的example中,我们将albem的title
域改为json的Album-Title
字段,artist的name
域改为json的Artist-Name
。实现这些,只需在main
中做如下改变:
1 mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
2 @Override
3 public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
4 if (field.getFullName().equals("com.studytrails.json.jackson.Artist#name"))
5 return "Artist-Name";
6 return super.nameForField(config, field, defaultName);
7 }
8
9 @Override
10 public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
11 if (method.getAnnotated().getDeclaringClass().equals(Album.class) && defaultName.equals("title"))
12 return "Album-Title";
13 return super.nameForGetterMethod(config, method, defaultName);
14 }
15 });
json结构如下:
1 {
2 "Album-Title" : "Kind Of Blue",
3 "links" : [ "link1", "link2" ],
4 "songs" : [ "So What", "Flamenco Sketches", "Freddie Freeloader" ],
5 "artist" : {
6 "Artist-Name" : "Miles Davis",
7 "birthDate" : "26 May 1926"
8 },
9 "musicians" : {
10 "Julian Adderley" : "Alto Saxophone",
11 "Miles Davis" : "Trumpet, Band leader",
12 "Paul Chambers" : "double bass"
13 }
14 }
现在来看一下Jackson如何处理null 域。我们为Artist
类新增三个属性,并且不赋值初始化。新增的属性如下:
1 public int age;
2 public String homeTown;
3 public List<String> awardsWon = new ArrayList<String>();
转换后的json结构如下:
1 {
2 "age" : 0,
3 "homeTown" : null,
4 "awardsWon" : [ ]
5 }
可以通过配置项来忽略空属性。
1 mapper.setSerializationInclusion(Include.NON_EMPTY);
以下是本节内容的完整代码:
1 import java.io.IOException;
2 import java.text.ParseException;
3 import java.text.SimpleDateFormat;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Date;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10
11 import com.fasterxml.jackson.annotation.JsonInclude.Include;
12 import com.fasterxml.jackson.databind.ObjectMapper;
13 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
14 import com.fasterxml.jackson.databind.SerializationFeature;
15 import com.fasterxml.jackson.databind.cfg.MapperConfig;
16 import com.fasterxml.jackson.databind.introspect.AnnotatedField;
17 import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
18
19 public class SerializationExample {
20
21 public static void main(String[] args) throws IOException, ParseException {
22 ObjectMapper mapper = new ObjectMapper();
23 Album album = new Album("Kind Of Blue");
24 album.setLinks(new String[] { "link1", "link2" });
25 List<String> songs = new ArrayList<String>();
26 songs.add("So What");
27 songs.add("Flamenco Sketches");
28 songs.add("Freddie Freeloader");
29 album.setSongs(songs);
30 Artist artist = new Artist();
31 artist.name = "Miles Davis";
32 SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
33 artist.birthDate = format.parse("26-05-1926");
34 album.setArtist(artist);
35 album.addMusician("Miles Davis", "Trumpet, Band leader");
36 album.addMusician("Julian Adderley", "Alto Saxophone");
37 album.addMusician("Paul Chambers", "double bass");
38 mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
39 mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
40 SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMM yyyy");
41 mapper.setDateFormat(outputFormat);
42 mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
43 @Override
44 public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
45 if (field.getFullName().equals("com.studytrails.json.jackson.Artist#name"))
46 return "Artist-Name";
47 return super.nameForField(config, field, defaultName);
48 }
49
50 @Override
51 public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
52 if (method.getAnnotated().getDeclaringClass().equals(Album.class) && defaultName.equals("title"))
53 return "Album-Title";
54 return super.nameForGetterMethod(config, method, defaultName);
55 }
56 });
57 mapper.setSerializationInclusion(Include.NON_EMPTY);
58 mapper.writeValue(System.out, album);
59 }
60 }
61
62 class Album {
63 private String title;
64 private String[] links;
65 private List<String> songs = new ArrayList<String>();
66 private Artist artist;
67 private Map<String , String> musicians = new HashMap<String , String>();
68
69 public Album(String title) {
70 this.title = title;
71 }
72
73 public String getTitle() {
74 return title;
75 }
76
77 public void setLinks(String[] links) {
78 this.links = links;
79 }
80
81 public String[] getLinks() {
82 return links;
83 }
84
85 public void setSongs(List<String> songs) {
86 this.songs = songs;
87 }
88
89 public List<String> getSongs() {
90 return songs;
91 }
92
93 public void setArtist(Artist artist) {
94 this.artist = artist;
95 }
96
97 public Artist getArtist() {
98 return artist;
99 }
100
101 public Map<String , String> getMusicians() {
102 return Collections.unmodifiableMap(musicians);
103 }
104
105 public void addMusician(String key, String value) {
106 musicians.put(key, value);
107 }
108 }
109
110 class Artist {
111 public String name;
112 public Date birthDate;
113 public int age;
114 public String homeTown;
115 public List<String> awardsWon = new ArrayList<String>();
116 }
本小节我们看到了如何将Java对象序列化成json的用法,下一小节,我们将讲解如何用Jackson的tree方法构建json.
用Tree Model构建JSON
用简单的Tree Model构建JSON有时是很有用的,例如你不想为你的JSON结构创建一个相应的Java Bean类。我们将再次使用上一节的example,有一个album类,他有一个Array的歌曲,一个艺术家和一个Array的作曲家。在建a tree之前,你先得做下面这些准备:
- 生成
JsonNodeFactory
对象来创建节点。 - 用
JsonFactory
生成JsonGenerator
对象,并指定输出方式,本例中我们的输出方式为输出到控制台。 - 生成一个
ObjectMapper
对象,它将用jsonGenerator
和root node创建JSON。
在一切准备好后,我们为album
创建单独的root node,注意,默认情况下ObjectMapper
没有为root node命名。
1 import java.io.IOException;
2
3 import com.fasterxml.jackson.core.JsonFactory;
4 import com.fasterxml.jackson.core.JsonGenerator;
5 import com.fasterxml.jackson.databind.JsonNode;
6 import com.fasterxml.jackson.databind.ObjectMapper;
7 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
8
9 public class SerializationExampleTreeModel {
10 public static void main(String[] args) throws IOException {
11 // Create the node factory that gives us nodes.
12 JsonNodeFactory factory = new JsonNodeFactory(false);
13
14 // create a json factory to write the treenode as json. for the example
15 // we just write to console
16 JsonFactory jsonFactory = new JsonFactory();
17 JsonGenerator generator = jsonFactory.createGenerator(System.out);
18 ObjectMapper mapper = new ObjectMapper();
19
20 // the root node - album
21 JsonNode album = factory.objectNode();
22 mapper.writeTree(generator, album);
23
24 }
25
26 }
结果将是:
1 {}
你没有看错,我们输入了这么多的代码,得到的回报仅仅是两个大括号。现在我们来开始真正构建JSON,先给album
的属性赋值,例如Album-Title
。
1 album.put("Album-Title", "Kind Of Blue");
现在JSON中有内容了:
1 {"Album-Title":"Kind Of Blue"}
现在开始给唱片集增加链接Array:
1 ArrayNode links = factory.arrayNode();
2 links.add("link1").add("link2");
3 album.put("links", links);
4
5 //JSON结果如下
6 {"Album-Title":"Kind Of Blue","links":["link1","link2"]}
接下来增加艺术家对象,记住,艺术家本身也是一个jsonObject:
1 ObjectNode artist = factory.objectNode();
2 artist.put("Artist-Name", "Miles Davis");
3 artist.put("birthDate", "26 May 1926");
4 album.put("artist", artist);
5
6 //JSON结果如下
7
8 {"Album-Title":"Kind Of Blue","links":["link1","link2"],
9 "artist":{"Artist-Name":"Miles Davis","birthDate":"26 May 1926"}}
目前为止我们还没有添加音乐家对象,音乐家不是Array形式而是一个musicians
类型对象:
1 ObjectNode musicians = factory.objectNode();
2 musicians.put("Julian Adderley", "Alto Saxophone");
3 musicians.put("Miles Davis", "Trumpet, Band leader");
4 album.put("musicians", musicians);
5
6 //JSON结果如下
7 {"Album-Title":"Kind Of Blue","links":["link1","link2"],
8 "artist":{"Artist-Name":"Miles Davis","birthDate":"26 May 1926"},
9 "musicians":{"Julian Adderley":"Alto Saxophone","Miles Davis":"Trumpet, Band leader"}}
以类似的方式你还可以添加其他元素,如果你有多个唱片集,你可以考虑通过循环生成一个album
数组.Tree Model方式生成JSON普遍要比从Java 对象序列化成JSON快。
Streaming Parser and Generator
Jackson提供了一个低层次的API来解析JSON字符串。这个API为每个JSON对象提供一个token。例如,JSON的开始标志{
是解析器提供的第一个对象。键值对是另一个单一对象。客户端代码能够利用tokens并得到json属性,或者如果需要的话,可以通过流构建出一个Java对象。低层次的API功能很强大,但是需要写更多的代码。下面我们提供了两个例子。第一个example演示json解析,第二个example演示json生成。
Json Parsing Example:
1 import java.io.IOException;
2 import java.net.MalformedURLException;
3 import java.net.URL;
4
5 import com.fasterxml.jackson.core.JsonFactory;
6 import com.fasterxml.jackson.core.JsonParser;
7 import com.fasterxml.jackson.core.JsonToken;
8
9 /**
10 * The aim of this class is to get the list of albums from free music archive
11 * (limit to 5)
12 *
13 */
14 public class StreamParser1 {
15 public static void main(String[] args) throws MalformedURLException, IOException {
16 // Get a list of albums from free music archive. limit the results to 5
17 String url = "http://freemusicarchive.org/api/get/albums.json?api_key=60BLHNQCAOUFPIBZ&limit=5";
18 // get an instance of the json parser from the json factory
19 JsonFactory factory = new JsonFactory();
20 JsonParser parser = factory.createParser(new URL(url));
21
22 // continue parsing the token till the end of input is reached
23 while (!parser.isClosed()) {
24 // get the token
25 JsonToken token = parser.nextToken();
26 // if its the last token then we are done
27 if (token == null)
28 break;
29 // we want to look for a field that says dataset
30
31 if (JsonToken.FIELD_NAME.equals(token) && "dataset".equals(parser.getCurrentName())) {
32 // we are entering the datasets now. The first token should be
33 // start of array
34 token = parser.nextToken();
35 if (!JsonToken.START_ARRAY.equals(token)) {
36 // bail out
37 break;
38 }
39 // each element of the array is an album so the next token
40 // should be {
41 token = parser.nextToken();
42 if (!JsonToken.START_OBJECT.equals(token)) {
43 break;
44 }
45 // we are now looking for a field that says "album_title". We
46 // continue looking till we find all such fields. This is
47 // probably not a best way to parse this json, but this will
48 // suffice for this example.
49 while (true) {
50 token = parser.nextToken();
51 if (token == null)
52 break;
53 if (JsonToken.FIELD_NAME.equals(token) && "album_title".equals(parser.getCurrentName())) {
54 token = parser.nextToken();
55 System.out.println(parser.getText());
56 }
57
58 }
59
60 }
61
62 }
63
64 }
65 }
Json Generation Example:
1 import java.io.File;
2 import java.io.FileWriter;
3 import java.io.IOException;
4
5 import com.fasterxml.jackson.core.JsonFactory;
6 import com.fasterxml.jackson.core.JsonGenerator;
7
8 /**
9 *
10 * In This example we look at generating a json using the jsongenerator. we will
11 * be creating a json similar to
12 * http://freemusicarchive.org/api/get/albums.json?
13 * api_key=60BLHNQCAOUFPIBZ&limit=1, but use only a couple of fields
14 *
15 */
16 public class StreamGenerator1 {
17 public static void main(String[] args) throws IOException {
18 JsonFactory factory = new JsonFactory();
19 JsonGenerator generator = factory.createGenerator(new FileWriter(new File("albums.json")));
20
21 // start writing with {
22 generator.writeStartObject();
23 generator.writeFieldName("title");
24 generator.writeString("Free Music Archive - Albums");
25 generator.writeFieldName("dataset");
26 // start an array
27 generator.writeStartArray();
28 generator.writeStartObject();
29 generator.writeStringField("album_title", "A.B.A.Y.A.M");
30 generator.writeEndObject();
31 generator.writeEndArray();
32 generator.writeEndObject();
33
34 generator.close();
35 }
36 }