diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
index 2ff337d4..1f788f84 100644
--- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
+++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
@@ -30,6 +30,7 @@ public class AlipayController {
@ApiOperation(value="获取故事信息", notes = "说明接口的详细信息,介绍,用途,注意事项等。")
@ApiMapping(value = "alipay.story.find")
+ // 参数必须封装在类中
public StoryVO getStory2(StoryParam story) {
StoryVO storyVO = new StoryVO();
storyVO.id = 1L;
@@ -43,6 +44,24 @@ public class AlipayController {
return story;
}
+ /**
+ * 演示文档表格树
+ * @param story
+ * @return
+ */
+ @ApiOperation(value="获取分类信息", notes = "演示表格树")
+ @ApiMapping(value = "alipay.category.get")
+ public Category getCategory(Category story) {
+ StoryVO storyVO = new StoryVO();
+ storyVO.id = 1L;
+ storyVO.name = "白雪公主";
+ storyVO.gmt_create = new Date();
+ Category category = new Category();
+ category.setCategoryName("娱乐");
+ category.setStory(storyVO);
+ return category;
+ }
+
@Data
public static class StoryVO {
@ApiModelProperty(value = "故事ID", example = "1")
@@ -52,4 +71,13 @@ public class AlipayController {
@ApiModelProperty(value = "创建时间", example = "2019-04-14 19:02:12")
private Date gmt_create;
}
+
+ @Data
+ public static class Category {
+ @ApiModelProperty(value = "分类名称", example = "娱乐")
+ private String categoryName;
+
+ @ApiModelProperty(value = "分类故事")
+ private StoryVO story;
+ }
}
diff --git a/sop-website/website-front/assets/lib/layuiext/Form.js b/sop-website/website-front/assets/lib/layuiext/Form.js
new file mode 100644
index 00000000..6868bb13
--- /dev/null
+++ b/sop-website/website-front/assets/lib/layuiext/Form.js
@@ -0,0 +1,159 @@
+/**
+ * 表单插件,使用方法:
+ * var myform = layui.Form('formId');
+ * myform.setData({...})
+ *
+ * var data = myform.getData();
+ *
+ * var name = myform.getData('name')
+ */
+layui.define(function (exports) {
+
+ /**
+ * form = new Form('formId');
+ * @param formId
+ * @constructor
+ */
+ var Form = function (formId) {
+ this.$form = $('#' + formId);
+ this.parseForm(this.$form);
+ }
+
+ Form.prototype = {
+ fire: function (eventName, data) {
+ var handler = this['on' + eventName];
+ handler && handler(data);
+ }
+ , opt: function (attr) {
+ return this[attr];
+ }
+ /**
+ * @private
+ */
+ , parseForm: function ($form) {
+ var that = this;
+ this.form = $form[0];
+ }
+
+ , getEls: function () {
+ return this.$form.find('input,select,textarea');
+ }
+
+ /**
+ * 同load(data)
+ */
+ , setData: function (data) {
+ this.loadData(data);
+ }
+
+ /**
+ * @private
+ */
+ , loadData: function (data) {
+ this.reset();
+ for (var name in data) {
+ var val = data[name];
+ var $el = this.$form.find('[name="' + name + '"]');
+
+ $el.each(function () {
+ var _$el = $(this);
+ if (_$el.is(':radio') || _$el.is(':checkbox')) {
+ _$el.prop('checked', false);
+ var elVal = _$el.val();
+
+ if ($.isArray(val)) {
+ for (var i = 0, len = val.length; i < len; i++) {
+ if (elVal == val[i]) {
+ _$el.prop('checked', true);
+ }
+ }
+ } else {
+ _$el.prop('checked', elVal == val);
+ }
+
+ } else {
+ _$el.val(val);
+ }
+
+ });
+ }
+ }
+ /**
+ * 清除表单中的值,清除错误信息
+ */
+ , clear: function () {
+ this.getEls().each(function () {
+ var _$el = $(this);
+ if (_$el.is(':radio') || _$el.is(':checkbox')) {
+ this.checked = false;
+ } else {
+ this.value = '';
+ }
+
+ var msg = _$el.data('msg');
+ if (msg) {
+ msg.text('');
+ }
+
+ });
+ }
+ /**
+ * 重置表单
+ */
+ , reset: function () {
+ var form = this.form;
+ if (form && form.reset) {
+ form.reset();
+ } else {
+ this.clear();
+ }
+ }
+ /**
+ * 获取表单数据,如果有fieldName参数则返回表单对应的值
+ * var id = form.getData('id') 等同于 var id = form.getData().id;
+ * @param {String} fieldName
+ * @return {Object} 返回JSON对象,如果有fieldName参数,则返回对应的值
+ */
+ , getData: function (fieldName) {
+ var that = this;
+ var data = {};
+
+ this.getEls().each(function () {
+ var value = that._getInputVal($(this));
+ if (value) {
+ var name = this.name;
+ var dataValue = data[name];
+ if (dataValue) {
+ if ($.isArray(dataValue)) {
+ dataValue.push(value);
+ } else {
+ data[name] = [dataValue, value];
+ }
+ } else {
+ data[name] = value;
+ }
+ }
+ });
+
+ if (typeof fieldName === 'string') {
+ return data[fieldName];
+ }
+
+ return data;
+ }
+ , _getInputVal: function ($input) {
+ if ($input.is(":radio") || $input.is(":checkbox")) {
+ if ($input.is(':checked')) {
+ return $input.val();
+ }
+ } else {
+ return $input.val();
+ }
+ }
+
+ }
+
+ exports('Form', function (formId) {
+ return new Form(formId);
+ });
+});
\ No newline at end of file
diff --git a/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css
new file mode 100644
index 00000000..584b2f8c
--- /dev/null
+++ b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.css
@@ -0,0 +1,18 @@
+.treeTable-empty {
+ width: 20px;
+ display: inline-block;
+}
+
+.treeTable-icon {
+ cursor: pointer;
+}
+
+.treeTable-icon .layui-icon-triangle-d:before {
+ content: "\e623";
+}
+
+.treeTable-icon.open .layui-icon-triangle-d:before {
+ content: "\e625";
+ background-color: transparent;
+}
+
diff --git a/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js
new file mode 100644
index 00000000..c95dec07
--- /dev/null
+++ b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js
@@ -0,0 +1,216 @@
+layui.define(['layer', 'table'], function (exports) {
+ var $ = layui.jquery;
+ var layer = layui.layer;
+ var table = layui.table;
+
+ var treetable = {
+ // 渲染树形表格
+ render: function (param) {
+ // 检查参数
+ if (!treetable.checkParam(param)) {
+ return;
+ }
+ // 获取数据
+ if (param.data) {
+ treetable.init(param, param.data);
+ } else {
+ // $.getJSON(param.url, param.where, function (res) {
+ // treetable.init(param, res.data);
+ // });
+ $.ajax({
+ url: param.url,
+ type: param.method || 'get',
+ data: param.where,
+ headers: param.headers || {},
+ dataType: "json",
+ success: function (res) {
+ treetable.init(param, res.data);
+ }
+ })
+ }
+ },
+ // 渲染表格
+ init: function (param, data) {
+ var mData = [];
+ var doneCallback = param.done;
+ var tNodes = data;
+ // 补上id和pid字段
+ for (var i = 0; i < tNodes.length; i++) {
+ var tt = tNodes[i];
+ if (!tt.id) {
+ if (!param.treeIdName) {
+ layer.msg('参数treeIdName不能为空', {icon: 5});
+ return;
+ }
+ tt.id = tt[param.treeIdName];
+ }
+ if (!tt.pid) {
+ if (!param.treePidName) {
+ layer.msg('参数treePidName不能为空', {icon: 5});
+ return;
+ }
+ tt.pid = tt[param.treePidName];
+ }
+ }
+
+ // 对数据进行排序
+ var sort = function (s_pid, data) {
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].pid == s_pid) {
+ var len = mData.length;
+ if (len > 0 && mData[len - 1].id == s_pid) {
+ mData[len - 1].isParent = true;
+ }
+ mData.push(data[i]);
+ sort(data[i].id, data);
+ }
+ }
+ };
+ sort(param.treeSpid, tNodes);
+
+ // 重写参数
+ param.url = undefined;
+ param.data = mData;
+ param.page = {
+ count: param.data.length,
+ limit: param.data.length
+ };
+ param.cols[0][param.treeColIndex].templet = function (d) {
+ var mId = d.id;
+ var mPid = d.pid;
+ var isDir = d.isParent;
+ var emptyNum = treetable.getEmptyNum(mPid, mData);
+ var iconHtml = '';
+ for (var i = 0; i < emptyNum; i++) {
+ iconHtml += '';
+ }
+ if (isDir) {
+ iconHtml += ' ';
+ } else {
+ iconHtml += '';
+ }
+ iconHtml += ' ';
+ var ttype = isDir ? 'dir' : 'file';
+ var vg = '';
+ return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + ''
+ };
+
+ param.done = function (res, curr, count) {
+ $(param.elem).next().addClass('treeTable');
+ $('.treeTable .layui-table-page').css('display', 'none');
+ $(param.elem).next().attr('treeLinkage', param.treeLinkage);
+ // 绑定事件换成对body绑定
+ /*$('.treeTable .treeTable-icon').click(function () {
+ treetable.toggleRows($(this), param.treeLinkage);
+ });*/
+ if (param.treeDefaultClose) {
+ treetable.foldAll(param.elem);
+ }
+ if (doneCallback) {
+ doneCallback(res, curr, count);
+ }
+ };
+
+ // 渲染表格
+ table.render(param);
+ },
+ // 计算缩进的数量
+ getEmptyNum: function (pid, data) {
+ var num = 0;
+ if (!pid) {
+ return num;
+ }
+ var tPid;
+ for (var i = 0; i < data.length; i++) {
+ if (pid == data[i].id) {
+ num += 1;
+ tPid = data[i].pid;
+ break;
+ }
+ }
+ return num + treetable.getEmptyNum(tPid, data);
+ },
+ // 展开/折叠行
+ toggleRows: function ($dom, linkage) {
+ var type = $dom.attr('lay-ttype');
+ if ('file' == type) {
+ return;
+ }
+ var mId = $dom.attr('lay-tid');
+ var isOpen = $dom.hasClass('open');
+ if (isOpen) {
+ $dom.removeClass('open');
+ } else {
+ $dom.addClass('open');
+ }
+ $dom.closest('tbody').find('tr').each(function () {
+ var $ti = $(this).find('.treeTable-icon');
+ var pid = $ti.attr('lay-tpid');
+ var ttype = $ti.attr('lay-ttype');
+ var tOpen = $ti.hasClass('open');
+ if (mId == pid) {
+ if (isOpen) {
+ $(this).hide();
+ if ('dir' == ttype && tOpen == isOpen) {
+ $ti.trigger('click');
+ }
+ } else {
+ $(this).show();
+ if (linkage && 'dir' == ttype && tOpen == isOpen) {
+ $ti.trigger('click');
+ }
+ }
+ }
+ });
+ },
+ // 检查参数
+ checkParam: function (param) {
+ if (!param.treeSpid && param.treeSpid != 0) {
+ layer.msg('参数treeSpid不能为空', {icon: 5});
+ return false;
+ }
+
+ if (!param.treeColIndex && param.treeColIndex != 0) {
+ layer.msg('参数treeColIndex不能为空', {icon: 5});
+ return false;
+ }
+ return true;
+ },
+ // 展开所有
+ expandAll: function (dom) {
+ $(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () {
+ var $ti = $(this).find('.treeTable-icon');
+ var ttype = $ti.attr('lay-ttype');
+ var tOpen = $ti.hasClass('open');
+ if ('dir' == ttype && !tOpen) {
+ $ti.trigger('click');
+ }
+ });
+ },
+ // 折叠所有
+ foldAll: function (dom) {
+ $(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () {
+ var $ti = $(this).find('.treeTable-icon');
+ var ttype = $ti.attr('lay-ttype');
+ var tOpen = $ti.hasClass('open');
+ if ('dir' == ttype && tOpen) {
+ $ti.trigger('click');
+ }
+ });
+ }
+ };
+
+ layui.link(layui.cache.base + 'treetable-lay/treetable.css');
+
+ // 给图标列绑定事件
+ $('body').on('click', '.treeTable .treeTable-icon', function () {
+ var treeLinkage = $(this).parents('.treeTable').attr('treeLinkage');
+ if ('true' == treeLinkage) {
+ treetable.toggleRows($(this), true);
+ } else {
+ treetable.toggleRows($(this), false);
+ }
+ });
+
+ exports('treetable', treetable);
+});
diff --git a/sop-website/website-front/pages/doc/doc.html b/sop-website/website-front/pages/doc/doc.html
index 8d6b0dc0..9e491b3d 100644
--- a/sop-website/website-front/pages/doc/doc.html
+++ b/sop-website/website-front/pages/doc/doc.html
@@ -32,6 +32,22 @@
.prop-type {
width: 50px;
}
+
+ .treeTable-empty{margin-left: 20px;}
+
+ /** 修改文件夹图标:未展开 */
+ .treeTable-icon .layui-icon-layer:before {
+ content: "";
+ }
+
+ /** 修改文件夹图标:展开 */
+ .treeTable-icon.open .layui-icon-layer:before {
+ content: "";
+ }
+ /* 修改文件图标:*/
+ .treeTable-icon .layui-icon-file:before {
+ content: "";
+ }
@@ -228,20 +244,7 @@
-
-
-
- 参数 |
- 类型 |
- 是否必填 |
- 最大长度 |
- 描述 |
- 示例值 |
-
-
-
-
-
+
@@ -313,20 +316,7 @@
-
-
-
- 参数 |
- 类型 |
- 是否必填 |
- 最大长度 |
- 描述 |
- 示例值 |
-
-
-
-
-
+
diff --git a/sop-website/website-front/pages/doc/doc.js b/sop-website/website-front/pages/doc/doc.js
index 17c85fd3..c504a982 100644
--- a/sop-website/website-front/pages/doc/doc.js
+++ b/sop-website/website-front/pages/doc/doc.js
@@ -1,5 +1,11 @@
-layui.use(['element', 'form'], function(){ //加载code模块
+layui.config({
+ base: '../../assets/lib/layuiext/module/'
+}).extend({
+ treetable: 'treetable-lay/treetable'
+}).use(['element', 'form', 'treetable'], function(){ //加载code模块
var form = layui.form;
+ var treetable = layui.treetable;
+ var $ = layui.jquery;
// key:module
var docItemStore = {};
@@ -85,39 +91,56 @@ layui.use(['element', 'form'], function(){ //加载code模块
}
function createRequestParameter(docItem) {
- var html = createParameterBody(docItem.requestParameters);
- $('#requestTbody').html(html);
+ var data = buildTreeData(docItem.requestParameters);
+ createTreeTable('treeTableReq', data);
}
function createResponseParameter(docItem) {
- var html = createParameterBody(docItem.responseParameters);
- $('#responseTbody').html(html);
+ var data = buildTreeData(docItem.responseParameters);
+ createTreeTable('treeTableResp', data);
}
- function createParameterBody(parameters) {
- /*
-
- 参数 |
- 类型 |
- 是否必填 |
- 最大长度 |
- 描述 |
- 示例值 |
-
- */
- var html = [];
+ function buildTreeData(parameters, parentId) {
+ var data = [];
+ parentId = parentId || 0;
for (var i = 0; i < parameters.length; i++) {
var parameter = parameters[i];
- html.push('\n' +
- ' '+parameter.name+' | \n' +
- ' '+parameter.type+' | \n' +
- ' '+(parameter.required ? '是' : '否')+' | \n' +
- ' - | \n' +
- ' '+parameter.description+' | \n' +
- ' ' + (parameter.example || parameter['x-example']) +' | \n' +
- '
')
+ parameter.id = parentId * 100 + (i + 1);
+ parameter.parentId = parentId;
+ data.push(parameter);
+ var refs = parameter.refs;
+ if (refs && refs.length > 0) {
+ var childData = buildTreeData(refs, parameter.id);
+ data = data.concat(childData);
+ }
}
- return html.join('');
+ return data;
+ }
+
+ function createTreeTable(id, data) {
+ var el = '#' + id;
+ $(el).text('');
+ treetable.render({
+ elem: el,
+ treeColIndex: 0,
+ treeSpid: 0,
+ treeIdName: 'id',
+ treePidName: 'parentId',
+ treeDefaultClose: false,
+ treeLinkage: false,
+ data: data,
+ page: false,
+ cols: [[
+ {field: 'name', title: '参数'}
+ ,{field: 'type', title: '类型', width: 80}
+ ,{field: 'required', title: '是否必填', width: 100, templet:function (row) {
+ return row.required ? '是' : '否';
+ }}
+ ,{field: 'maxLength', title: '最大长度', width: 100}
+ ,{field: 'description', title: '描述', width: 200}
+ ,{field: 'paramExample', title: '示例值', width: 200}
+ ]]
+ });
}
function createResponseCode(docItem) {
diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java
index 17a6c10b..658c36c8 100644
--- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java
+++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java
@@ -2,6 +2,9 @@ package com.gitee.sop.websiteserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.List;
/**
* 参数 类型 是否必填 最大长度 描述 示例值
@@ -9,8 +12,10 @@ import lombok.Data;
*/
@Data
public class DocParameter {
+ private String module;
private String name;
private String type;
+ private String maxLength = "-";
private boolean required;
private String description;
private String example = "";
@@ -18,4 +23,9 @@ public class DocParameter {
@JSONField(name = "x-example")
private String x_example = "";
+ private List refs;
+
+ public String getParamExample() {
+ return StringUtils.isBlank(example) ? x_example : example;
+ }
}
diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java
index 85000a79..85f1550c 100644
--- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java
+++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java
@@ -7,9 +7,12 @@ import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.DocModule;
import com.gitee.sop.websiteserver.bean.DocParameter;
import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -66,9 +69,7 @@ public class SwaggerDocParser implements DocParser {
docItem.setDescription(docInfo.getString("description"));
String moduleName = this.buildModuleName(docInfo, docRoot);
docItem.setModule(moduleName);
- Optional parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
- JSONArray parameters = parametersOptional.orElse(new JSONArray());
- List docParameterList = parameters.toJavaList(DocParameter.class);
+ List docParameterList = this.buildRequestParameterList(docInfo, docRoot);
docItem.setRequestParameters(docParameterList);
List responseParameterList = this.buildResponseParameterList(docInfo, docRoot);
@@ -86,23 +87,88 @@ public class SwaggerDocParser implements DocParser {
return title;
}
+ protected List buildRequestParameterList(JSONObject docInfo, JSONObject docRoot) {
+ Optional parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
+ JSONArray parameters = parametersOptional.orElse(new JSONArray());
+ List docParameterList = new ArrayList<>();
+ for (int i = 0; i < parameters.size(); i++) {
+ JSONObject fieldJson = parameters.getJSONObject(i);
+ DocParameter docParameter = fieldJson.toJavaObject(DocParameter.class);
+ docParameterList.add(docParameter);
+ }
+
+ Map> collect = docParameterList.stream()
+ .filter(docParameter -> docParameter.getName().contains("."))
+ .map(docParameter -> {
+ String name = docParameter.getName();
+ int index = name.indexOf('.');
+ String module = name.substring(0, index);
+ String newName = name.substring(index + 1);
+ DocParameter ret = new DocParameter();
+ BeanUtils.copyProperties(docParameter, ret);
+ ret.setName(newName);
+ ret.setModule(module);
+ return ret;
+ })
+ .collect(Collectors.groupingBy(DocParameter::getModule));
+
+ collect.entrySet().stream()
+ .forEach(entry -> {
+ DocParameter moduleDoc = new DocParameter();
+ moduleDoc.setName(entry.getKey());
+ moduleDoc.setType("object");
+ moduleDoc.setRefs(entry.getValue());
+ docParameterList.add(moduleDoc);
+ });
+
+ List ret = docParameterList.stream()
+ .filter(docParameter -> !docParameter.getName().contains("."))
+ .collect(Collectors.toList());
+
+ return ret;
+ }
+
protected List buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) {
String responseRef = getResponseRef(docInfo);
- List respParameterList = new ArrayList<>();
+ List respParameterList = Collections.emptyList();
if (StringUtils.isNotBlank(responseRef)) {
- JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef);
- JSONObject properties = responseObject.getJSONObject("properties");
- Set fieldNames = properties.keySet();
- for (String fieldName : fieldNames) {
- JSONObject fieldInfo = properties.getJSONObject(fieldName);
- DocParameter respParam = fieldInfo.toJavaObject(DocParameter.class);
- respParam.setName(fieldName);
- respParameterList.add(respParam);
- }
+ respParameterList = this.buildDocParameters(responseRef, docRoot);
}
return respParameterList;
}
+ protected List buildDocParameters(String ref, JSONObject docRoot) {
+ JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(ref);
+ JSONObject properties = responseObject.getJSONObject("properties");
+ Set fieldNames = properties.keySet();
+ List docParameterList = new ArrayList<>();
+ for (String fieldName : fieldNames) {
+ /*
+ {
+ "description": "分类故事",
+ "$ref": "#/definitions/StoryVO",
+ "originalRef": "StoryVO"
+ }
+ */
+ JSONObject fieldInfo = properties.getJSONObject(fieldName);
+ DocParameter respParam = fieldInfo.toJavaObject(DocParameter.class);
+ respParam.setName(fieldName);
+ docParameterList.add(respParam);
+ String originalRef = getRef(fieldInfo);
+ if (StringUtils.isNotBlank(originalRef)) {
+ List refs = buildDocParameters(originalRef, docRoot);
+ respParam.setRefs(refs);
+ }
+ }
+ return docParameterList;
+ }
+
+ private String getRef(JSONObject fieldInfo) {
+ return Optional.ofNullable(fieldInfo)
+ .map(jsonObject -> jsonObject.getString("originalRef"))
+ .orElse(null);
+ }
+
protected String getResponseRef(JSONObject docInfo) {
String ref = Optional.ofNullable(docInfo.getJSONObject("responses"))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("200")))