用 JSON 表现树的结构兼谈队列、堆栈的练习(一)

K/V 与 Array

接触 JSON 的人都知道,JSON 可通过 K/V(Key/Value) 结构很直观地表现一棵树,因为 V 可以“包含”另外一个 K/V 从而不断嵌套下去形成“树状”的结构。但 V 不一定必须为另外一个 K/V,而是可以为 Array 数组。数组中由可以“包含”更多的 K/V 或者又是数组类型——也是可以的。如此反复下去,可以形成层数很深的一棵树。例如

{
  aa : {
	cc :[
               “dd”,
               {
                   ee: true, ff: “hihi”
               }
          ]
  },
  bb: [  ]
}

这里说的树是指无序树,甚至根节点也没有,不过没关系,在最外一层加上便是。

比较微妙的是 JSON 允许了数组和 K/V 相互嵌套。下级子节点应该用 K/V 来装,还是用 Array 来装呢?又怎么理解数组和 K/V 的关系呢?个人理解,数组本质上也可以归纳其为 K/V。我们一般讨论数组时候还会接触到数组的索引,例如 arr[0] = a, arr[1] = b,索引便是 key 的一种,只不过我们通常 JSON 里面的 key 为字符串,实际上为 int 类型也是允许的。相较而言,数组结构比 K/V 的简单,是 K/V 的一种简化。当然我这种只是“大而化之”的理解,——实际上它们差别很多,好比它们的数据结就显著不同:数组仅仅是一个线性表;K/V 会复杂的多,一般要经过多步 hash 的运算。

再看看上面的 JSON 例子,看起来这个 JSON 想要表达很多东西,最外层是个 K/V,里面的 aa 是下一层的 K/V,但 bb 却是数组,——似乎结构有点混乱。如果用来表现一个树,显然也是一颗“混乱的树”。如果实际开发遇到这样结构的设计,那肯定有问题的,需要好好简化的。不过,无论怎么简化,一旦引入树的概念后,好像还是会有矛盾的地方,例如 K/V 和 Array 两者都可以延伸一下级节点,它们之间有什么不同呢?什么时候该用 K/V 呢?什么时候又该使用 Array 呢?这并无标准答案,JSON 自身并不会说明清楚或者强制要求。再例如 cc 这个数组,第一个元素是字符串,第二个元素是 K/V。因为我们知道,JSON 包含的元素可允许是不同类型的,即混合多种类型的值为一个数组——那样本身是没问题的。但结合树的概念的话问题就来了,是否 K/V 就必须是引出下级树节点吗?——我大可以理解为 JSON 的一个值,她是 K/V 类型,那也是合法的啊,同理数组也不一定引出下级的节点,当前只是表现同类对象的集合,——那也是完全合法的。所以怎么定义这是个树节点,还是说一个 JSON 值?二义性的问题由此产生了。

为解决这个二义性的问题,我们可以对 JSON 作适当的约定,以便更清晰地和准确地反映一棵树。首先节点用 K/V 表示;Children 是下级节点的数组,是容器。它不能是其他的类型如 map 的类型,只能是 Array。只有 最外一层 和 父容器名为 children 的数组,里面的 K/V 才是树节点。一个节点可以有零个或一个 children 的 K/V,且 V 必然是数组。

如下便是一个我们约束定义的树:

 

[ {
    'name' : "关于我们",
    'id' : 'about',
    'children' : [ 
        {
            name : "公司历程",
            id : 'history'
        },
        {
            name : "企业文化",
            id : 'cluture'
        }
    ]
}, {
    'name' : "美食天地",
    'id' : 'product',
    'children' : [ 
        {
            name : "最新美食",
            id : 'new',
            'children' : [
                {
                    'id' : 'yuecai',
                    'name' : '粤菜'
                },
                {
                    'id' : 'yuecai',
                    'name' : '湘菜'
                }
            ]
        },
        {
            name : "热门菜谱",
            id : 'hot'
        }
    ]
}, 
{
    'name' : "最新资讯",
    'id' : 'news'
}, 
{
    'name' : "招聘信息",
    'id' : 'hr'
}, {
    'name' : "联系我们",
    'id' : 'contact'
}]

值得注意的是该结构最外一层为 Array 而不是 K/V。

遍历 JSON

JSON 本身乃 JavaScript 的产物,虽然也有序列化和反序列化的过程,但使用起来还是比较自然、“原生原味”的。

这里重点说说 Java 世界处理 JSON 的话题。当 JSON 字符串经过解析器反序列化之后,可得到 Java 识别的类型。如果引入三方包,就有其自定义的类型(如 JSONArray、JSONObject)。但是我们这里不使用三方包的类型来说明问题(虽然可能都是“同理”得出一致的结论),——因为那又牵涉到该使用哪个三方包的问题(选择困难症患者-_-)。

于 Java 而言与 JSON 对应的结构一般自然的选择是 Map/List 组合——本文就拿 Map/List 就好了。这里用泛型可以加强说明所包含元素的类型是什么,使之更加直观和清晰,即 Map,其中,String 是 key 的类型,我们知道 JSON 的key 类型就是字符串类型;Object 便是 Value 的类型,可以是合法的 JSON 值(字符串、数字、null),或者是另外一个 Map 或 List。至于 List 的泛型便是嵌套的 List<Map>。于是,我们可以写一个方法(或者第三方包),JSON 字符串被解析之后,得到 Map/List 结构的 Java 类型,变成 Java 可以理解的“树”。应该怎么遍历的这棵树呢?最简单的方法,莫过于递归这个 Map/List。

好比现在输入这段 JSON,这是网站的配置文件:

 

{
	"site" : {
		"titlePrefix" : "大华•川式料理",
		"keywords" : "大华•川式料理",
		"description" : "大华•川式料理饮食有限公司于2015年成立,本公司目标致力打造中国新派川菜系列。炜爵爷川菜料理系列的精髓在于清、鲜、醇、浓、香、烫、酥、嫩,擅用麻辣。在服务出品环节上,团队以ISO9000为蓝本建立标准化餐饮体系,务求以崭新的姿态面向社会各界人仕,提供更优质的服务以及出品。炜爵爷宗旨:麻辣鲜香椒,美味有诀窍,靓油用一次,精品煮御赐。 ",
		"footCopyright":"dsds" 
	},
	"dfd":{
		"dfd":'fdsf',
		"id": 888,
		"dfdff":{
			"dd":'fd'
		}
	},
	"clientFullName":"大华•川式料理",
	"clientShortName":"大华",
	"isDebug": true,
	"data" : {
		"newsCatalog_Id" : 6,
		"jobCatalog_Id" :7
	}
}

送入 JSON 解析器得到 Map:

这里暂忽略 JSON 解析器的原理。先接着看看遍历 JSON 的过程。假设我们要把所有 key 列出来

@SuppressWarnings("unchecked")
public void travel(Map<String, Object> map) {
    for (String key : map.keySet()) {
        Object obj = map.get(key);
        System.out.println("The key is:" + key);

        if (obj != null && obj instanceof Map) {
            Map<String, Object> _map = (Map<String, Object>) obj;
            travel(_map);     
        }
    }
}

打印结果如下

前面说到,我们讨论的是树结构,已经有这样的约定:如果遇到 Key 为 children 且 value 为数组元素的话,那就下级节点,数组里的都是子节点 K/V。否则就是普通的一个 JSON 数组。

@SuppressWarnings("unchecked")
public void travel(Map<String, Object> map) {
	for (String key : map.keySet()) {
		Object obj = map.get(key);
		System.out.println("The key is:" + key);

		if (obj != null && obj instanceof Map) {
			Map<String, Object> _map = (Map<String, Object>) obj;
			if (_map.get(children) != null && _map.get(children) instanceof List) {
				List<Map<String, Object>> list = (List<Map<String, Object>>) _map.get(children);

				for (Map<String, Object> __map : list)
					travel(__map);
			}
		}
	}
}

与前面的函数相比只是增加了 children 的判断,然后遍历 children 里面各项的 map。——一切都非常简单是吧?可以说毫无惊艳之处。不过读者可试着改造一下,把当前支持 Map<String, Object> map 类型的参数改为List<Map<String, Object>> list 的,看看遍历过程有什么不同。

分析树

现在不妨把需求的难度提高那么一丢丢:希望可以完整记下节点的完整的“路径”和层级。文章到这里写得太长太冗长了,笔者还是赶紧给出代码,赶紧收尾。

输入 JSON 数组:

 

[ {
	'name' : "关于我们",
	'id' : 'about',
	'children' : [ 
		{
			name : "公司历程",
			id : 'history'
		},
		{
			name : "企业文化",
			id : 'cluture'
		}
	]
}, {
	'name' : "美食天地",
	'id' : 'product',
	'children' : [ 
		{
			name : "最新美食",
			id : 'new',
			'children' : [
				{
					'id' : 'yuecai',
					'name' : '粤菜'
				},
				{
					'id' : 'yuecai',
					'name' : '湘菜'
				}
			]
		},
		{
			name : "热门菜谱",
			id : 'hot'
		}
	]
}, 
{
	'name' : "最新资讯",
	'id' : 'news'
}, 
{
	'name' : "招聘信息",
	'id' : 'hr'
}, {
	'name' : "联系我们",
	'id' : 'contact'
}]

这里给出前一小节的答案,就是遍历 List 的,并增加了功能。

/**
     * 分析这棵树,为每个节点增加 fullPath 和 level 属性,分别表示完整的路径和层数
     * 
     * @param list
     *            输入的树,必须为 List
     * @param superNode
     *            父级节点
     * @param level
     *            层数
     */
@SuppressWarnings("unchecked")
public void travelList(List<Map<String, Object>> list, Map<String, Object> superNode, int level) {
	for (Map<String, Object> map : list) {
		if (map != null) {
			String currerntPath = (superNode != null ? superNode.get("fullPath").toString() : "") + "/" + map.get(id).toString();
			map.put("fullPath", currerntPath);
			map.put("level", level);

			// 记录父级信息
			List<String> supers = new ArrayList<>();
			map.put("supers", supers);

			if (superNode != null) {
				supers.addAll((List<String>) superNode.get("supers"));
				supers.add(superNode.get("fullPath") + ":" + superNode.get("name")); // 仅记录 id 和 name
			}

			if (map.get(children) != null && map.get(children) instanceof List)
				travelList((List<Map<String, Object>>) map.get(children), map, level + 1);
		}
	}
}

结果是

好吧,我承认,这也不是太难的例子,仍然不外乎 for 循环+ 递归。下一篇要写 Stack 的内容了。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 生成结构JSON 代码可以使用递归方法来实现。 以下是一个示例代码: ```java import com.fasterxml.jackson.databind.ObjectMapper; public class TreeNode { String name; List<TreeNode> children; public TreeNode(String name) { this.name = name; this.children = new ArrayList<>(); } public void addChild(TreeNode child) { this.children.add(child); } } public class TreeGenerator { public static void main(String[] args) { // 创建根节点 TreeNode root = new TreeNode("root"); // 创建子节点 TreeNode node1 = new TreeNode("node1"); TreeNode node2 = new TreeNode("node2"); TreeNode node3 = new TreeNode("node3"); // 将子节点添加到根节点的 children 中 root.addChild(node1); root.addChild(node2); root.addChild(node3); // 创建孙子节点 TreeNode node4 = new TreeNode("node4"); TreeNode node5 = new TreeNode("node5"); TreeNode node6 = new TreeNode("node6"); // 将孙子节点添加到第一个子节点的 children 中 node1.addChild(node4); node1.addChild(node5); node1.addChild(node6); // 使用 Jackson 库将结构转换为 JSON 字符串 ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(root); System.out.println(json); } } ``` 在这个代码中,我们定义了一个 TreeNode 类表示节点,其中包含了节点名称和子节点的列表。然后我们使用递归的方法来构建结构,并使用 Jackson 库将其转换为 JSON 字符串。 运行这个代码后,你将得到一个类似于下面的输出: ```json ### 回答2: 根据您的要求,我为您提供一个使用Java编写的生成结构JSON代码示例。 首先,我们需要定义一个节点类Node,该类包含节点的标识符id、节点名称name以及子节点的列表children。 ```java import java.util.ArrayList; import java.util.List; class Node { private String id; private String name; private List<Node> children; public Node(String id, String name) { this.id = id; this.name = name; this.children = new ArrayList<>(); } public void addChild(Node node) { children.add(node); } // 这里省略了getter和setter方法 // 将节点及其子节点转化为JSON格式的字符串 public String toJson() { StringBuilder json = new StringBuilder(); json.append("{"); json.append("\"id\":\"").append(id).append("\","); json.append("\"name\":\"").append(name).append("\""); if (!children.isEmpty()) { json.append(",\"children\":["); for (int i = 0; i < children.size(); i++) { if (i > 0) { json.append(","); } json.append(children.get(i).toJson()); } json.append("]"); } json.append("}"); return json.toString(); } } ``` 接下来,我们可以使用Node类构建结构的节点,并将其转化为JSON格式的字符串输出。 ```java public class Main { public static void main(String[] args) { Node root = new Node("1", "Root"); Node child1 = new Node("2", "Child 1"); Node child2 = new Node("3", "Child 2"); Node child3 = new Node("4", "Child 3"); Node grandchild1 = new Node("5", "Grandchild 1"); Node grandchild2 = new Node("6", "Grandchild 2"); child1.addChild(grandchild1); child2.addChild(grandchild2); root.addChild(child1); root.addChild(child2); root.addChild(child3); String json = root.toJson(); System.out.println(json); } } ``` 以上代码通过创建节点对象,添加子节点,并使用toJson方法将节点转化为JSON格式的字符串。您可以根据自己的需求修改节点的属性和结构。 ### 回答3: Sure, 下面是一个使用Java编写的生成结构JSON的示例代码: ```java import com.google.gson.JsonArray; import com.google.gson.JsonObject; public class TreeNode { private int id; private String name; private TreeNode parent; private List<TreeNode> children; public TreeNode(int id, String name) { this.id = id; this.name = name; this.children = new ArrayList<>(); } public void setParent(TreeNode parent) { this.parent = parent; } public void addChild(TreeNode child) { child.setParent(this); children.add(child); } public JsonObject toJson() { JsonObject json = new JsonObject(); json.addProperty("id", id); json.addProperty("name", name); if (parent != null) { json.addProperty("parentId", parent.getId()); } if (children.size() > 0) { JsonArray childrenArray = new JsonArray(); for (TreeNode child : children) { childrenArray.add(child.toJson()); } json.add("children", childrenArray); } return json; } public int getId() { return id; } public String getName() { return name; } public TreeNode getParent() { return parent; } public List<TreeNode> getChildren() { return children; } public static void main(String[] args) { TreeNode root = new TreeNode(1, "Root"); TreeNode node1 = new TreeNode(2, "Node 1"); TreeNode node2 = new TreeNode(3, "Node 2"); TreeNode node3 = new TreeNode(4, "Node 3"); root.addChild(node1); root.addChild(node2); node1.addChild(node3); JsonObject json = root.toJson(); System.out.println(json.toString()); } } ``` 在这个示例代码中,TreeNode类表示中的一个节点,包含id、name、parent和children等属性。节点可以通过addChild()方法添加子节点,并通过toJson()方法将节点转换为JSON对象。使用Gson库可以将JSON对象打印出来。 在main方法中,我们创建了一个结构,并将根节点转换为JSON对象并打印出来。您可以根据需要修改节点的属性和结构。希望这个代码对您有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sp42a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值