Java-Server Android JavaScript Vue.js
- 2.1 Process
- 2.2 Client request
- 2.3 Server operate
- 2.4 Client resolve
- 2.5 Requests
- 2.6 Responses
- 3.1 Operation
- 3.2 Function
- 4.1 Download and unzip
- 4.2 Import tables
- 4.3 Run server
- 4.4 Run client
- 4.4 Operate app
- 5.1 Recommend
- 5.2 Author
- 5.3 Download
- 5.4 Update
- 5.5 Star&Fork
APIJSON is a JSON Transmission Structure Protocol.
You can set any JSON structure and request your server, and the server will response a JSON with the structure you had set.
You can get any data by requesting server just once. It's very convenient and flexible, and does not require a special api or multiple requests.
It provides CRUD(read and write), Fuzzy Search, Remote Function Calls, Rights Management and so on. And you can save duplicate data and improve transmission speed as well!
Now you can realize JSON Transmissions without any api or document anymore!
Client developers will no longer be suffered from various error in documents, and don't have to communicate with server developers about apis or documents anymore!
And server developers no longer have to write new apis and documents for compatibility with legacy apps! And they will no longer be endlessly disturbed by client developers at any time!
Request:
{
"User":{
}
}
Response:
{
"User":{
"id":38710,
"sex":0,
"name":"TommyLemon",
"certified":true,
"tag":"Android&Java",
"phone":13000038710,
"head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
"date":1485948110000,
"pictureList":[
"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
"http://common.cnblogs.com/images/icon_weibo_24.png"
]
},
"code":200,
"msg":"success"
}
Request:
{
"[]":{
"count":3, //just get 3 results
"User":{
"@column":"id,name" //just get ids and names
}
}
}
Response:
{
"[]":[
{
"User":{
"id":38710,
"name":"TommyLemon"
}
},
{
"User":{
"id":70793,
"name":"Strong"
}
},
{
"User":{
"id":82001,
"name":"Android"
}
}
],
"code":200,
"msg":"success"
}
Request:
{
"Moment":{
},
"User":{
"id@":"Moment/userId" //User.id = Moment.userId
}
}
Response:
{
"Moment":{
"id":12,
"userId":70793,
"date":"2017-02-08 16:06:11.0",
"content":"1111534034"
},
"User":{
"id":70793,
"sex":0,
"name":"Strong",
"tag":"djdj",
"head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000",
"contactIdList":[
38710,
82002
],
"date":"2017-02-01 19:21:50.0"
},
"code":200,
"msg":"success"
}
Request:
{
"[]":{ //request an Array
"page":0, //Array condition
"count":2,
"Moment":{ //request an Object named Moment
"content$":"%a%" //Object condition, search the Moments in which their contents contain 'a'
},
"User":{
"id@":"/Moment/userId", //User.id = Moment.userId, reference path with it's grandfather's path omitted
"@column":"id,name,head" //set the columns in the response
},
"Comment[]":{ //request an Array named Comment and extract Comments
"count":2,
"Comment":{
"momentId@":"[]/Moment/id" //Comment.momentId = Moment.id, full reference path
}
}
}
}
Response:
{
"[]":[
{
"Moment":{
"id":15,
"userId":70793,
"date":1486541171000,
"content":"APIJSON is a JSON Transmission Structure Protocol…",
"praiseUserIdList":[
82055,
82002,
82001
],
"pictureList":[
"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
"http://common.cnblogs.com/images/icon_weibo_24.png"
]
},
"User":{
"id":70793,
"name":"Strong",
"head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"
},
"Comment[]":[
{
"id":176,
"toId":166,
"userId":38710,
"momentId":15,
"date":1490444883000,
"content":"thank you"
},
{
"id":1490863469638,
"toId":0,
"userId":82002,
"momentId":15,
"date":1490863469000,
"content":"Just do it"
}
]
},
{
"Moment":{
"id":58,
"userId":90814,
"date":1485947671000,
"content":"This is a Content...-435",
"praiseUserIdList":[
38710,
82003,
82005,
93793,
82006,
82044,
82001
],
"pictureList":[
"http://static.oschina.net/uploads/img/201604/22172507_aMmH.jpg"
]
},
"User":{
"id":90814,
"name":7,
"head":"http://static.oschina.net/uploads/user/51/102723_50.jpg?t=1449212504000"
},
"Comment[]":[
{
"id":13,
"toId":0,
"userId":82005,
"momentId":58,
"date":1485948050000,
"content":"This is a Content...-13"
},
{
"id":77,
"toId":13,
"userId":93793,
"momentId":58,
"date":1485948050000,
"content":"This is a Content...-77"
}
]
}
],
"code":200,
"msg":"success"
}
Process | Previous way | APIJSON |
---|---|---|
Transmission | Server developers edit apis and update documents, then client developers request server and parse responses according to the documents | Client developers request server and parse responses for their requirements. No inteface! No document! No communication for any api or document between client and server developers! |
Compatibility | Server developers add new apis tagged with v2 and update documents | Nothing need to do! |
Client request | Previous way | APIJSON |
---|---|---|
Requirement | Client developers append key-value pairs to an url for a request in documents | Client developers append JSON to the url for their requirements |
Structure | base_url/get/table_name? key0=value0&key1=value1... Only one table_name can be contained in an URL |
base_url/get/ { TableName0:{ key0:value0, key1:value1, ... }, TableName1:{ ... } ... } You can add TableNames as many as you want after an URL |
URL | Different urls for different requests. The more diffirent kinds of requests, the more different urls | One url for one method(GET,POST...), most requests use the same URL of the 7 common ones |
Key-Value Pair | key=value | key:value |
Server operate | Previous way | APIJSON |
---|---|---|
Parse and response | Get key-value pairs and query the database with them by the default way, then encapsulate a JSON, finally return the JSON to clients | Just return what Parser#parse returned |
Way of setting JSON structure of Response | Designed in servers and cannot be modified by clients | Designed by clients and cannot be modified by servers |
Client resolve | Previous way | APIJSON |
---|---|---|
View structure | Search documents or view logs after responses for requests | Just view the requests, and viewing logs after responses for requests is also supported |
Operate | Parse JSON String from responses | Parse with JSONResponse or use previous way |
Requests | Previous way | APIJSON |
---|---|---|
User | base_url/get/user?id=38710 | base_url/get/ { "User":{ "id":38710 } } |
Moment and it's publisher(User) | Request twice Moment: base_url/get/moment?userId=38710 User: base_url/get/user?id=38710 |
Just request once base_url/get/ { "Moment":{ "userId":38710 }, "User":{ "id":38710 } } |
User list | base_url/get/user/list? page=0&count=3&sex=0 |
base_url/get/ { "User[]":{ "page":0, "count":3, "User":{ "sex":0 } } } |
A list, each item contains a Moment, a publisher(User) and a list of top 3 Comments |
The Moment must contains an User Object and a Comment Array base_url/get/moment/list? page=0&count=3&commentCount=3 |
base_url/get/ { "[]":{ "page":0, "count":3, "Moment":{}, "User":{ "id@":"/Moment/userId" }, "Comment[]":{ "count":3, "Comment":{ "momentId@":"[]/Moment/id" } } } } |
A list, each item contains a Moment, the same publisher(User) and a list of top 3 Comments |
Each Moment must contains an User Object and a Comment Array base_url/get/moment/list? page=0&count=3 &commentCount=3&userId=38710 |
Here are several ways: ① Change "Moment":{}, "User":{"id@":"/Moment/userId"} to "Moment":{"userId":38710}, "User":{"id":38710} ② Or save repeated Users by this way base_url/get/ { "User":{ "id":38710 }, "[]":{ "page":0, "count":3, "Moment":{ "userId":38710 }, "Comment[]":{ "count":3, "Comment":{ "momentId@":"[]/Moment/id" } } } } ③ If the User is already obtained, you can also save all repeated User by this way base_url/get/ { "[]":{ "page":0, "count":3, "Moment":{ "userId":38710 }, "Comment[]":{ "count":3, "Comment":{ "momentId@":"[]/Moment/id" } } } } |
Responses | Previous way | APIJSON |
---|---|---|
User | { "data":{ "id":38710, "name":"xxx", ... }, "code":200, "msg":"success" } |
{ "User":{ "id":38710, "name":"xxx", ... }, "code":200, "msg":"success" } |
Moment and it's publisher(User) | Get the Moment from the first response, and take it's userId as the value of User's id, then send the second request to get the User Moment: { "data":{ "id":235, "content":"xxx", ... }, "code":200, "msg":"success" } User: { "data":{ "id":38710, "name":"xxx", ... }, "code":200, "msg":"success" } |
Response only once, and no longer needs to waiting too long, relating 2 responses, switching threads and so on { "Moment":{ "id":235, "content":"xxx", ... }, "User":{ "id":38710, "name":"xxx", ... }, "code":200, "msg":"success" } |
User list | { "data":[ { "id":38710, "name":"xxx", ... }, { "id":82001, ... }, ... ], "code":200, "msg":"success" } |
{ "User[]":[ { "id":38710, "name":"xxx", ... }, { "id":82001, ... }, ... ], "code":200, "msg":"success" } |
A list, each item contains a Moment, a publisher(User) and a list of top 3 Comments |
Eech Moment must contains it's publisher(User) and a list of top 3 Comments { "data":[ { "id":235, "content":"xxx", ..., "User":{ ... }, "Comment":[ ... ] }, { "id":301, "content":"xxx", ..., "User":{ ... }, ... }, ... ], "code":200, "msg":"success" } |
1.Flexible structures, you can combine the Objects and Arrays as you want 2.Loose couplings, the structure is clearer { "[]":[ { "Moment":{ "id":235, "content":"xxx", ... }, "User":{ ... }, "Comment[]":[ ... ] }, { "Moment":{ "id":301, "content":"xxx", ... }, "User":{ ... }, ... }, ... ], "code":200, "msg":"success" } |
A list, each item contains a Moment, the same publisher(User) and a list of top 3 Comments |
1.Many repeated Users, a waste of data traffic and server performance 2.Difficult to optimize since the needs of expanding apis and writing documents, and then calling the apis according to the documents { "data":[ { "id":235, "content":"xxx", ..., "User":{ "id":38710, "name":"Tommy" ... }, "Comment":[ ... ] ... }, { "id":470, "content":"xxx", ..., "User":{ "id":38710, "name":"Tommy" ... }, "Comment":[ ... ] ... }, { "id":511, "content":"xxx", ..., "User":{ "id":38710, "name":"Tommy" ... }, "Comment":[ ... ] ... }, { "id":595, "content":"xxx", ..., "User":{ "id":38710, "name":"Tommy" ... }, "Comment":[ ... ] ... }, ... ], "code":200, "msg":"success" } |
Differences responses for the requests above: ① Common request { "[]":[ { "Moment":{ "id":235, "content":"xxx", ... }, "User":{ "id":38710, "name":"Tommy" ... }, "Comment[]":[ ... ] }, ... ], "code":200, "msg":"success" } ② Save repeated Users { "User":{ "id":38710, "name":"Tommy", ... }, "[]":[ { "Moment":{ "id":235, "content":"xxx", ... }, "Comment[]":[ ... ] }, ... ], "code":200, "msg":"success" } ③ Save all repeated Users { "[]":[ { "Moment":{ "id":235, "content":"xxx", ... }, "Comment[]":[ ... ] }, ... ], "code":200, "msg":"success" } |
1.base_url指基地址,一般是顶级域名,其它分支url都是在base_url后扩展。如base_url:http://apijson.cn:8080/ ,对应的GET分支url:http://apijson.cn:8080/get/ 。下同。
2.请求中的key或value任意一个为null值时,这个 key:value键值对 被视为无效。下同。
3.请求中的 / 需要转义。JSONRequest.java已经用URLEncoder.encode转义,不需要再写;但如果是浏览器或Postman等直接输入url/request,需要把request中的所有 / 都改成 %252F 。下同。
4.code,指返回结果中的状态码,200表示成功,其它都是错误码,值全部都是HTTP标准状态码。下同。
5.msg,指返回结果中的状态信息,对成功结果或错误原因的详细说明。下同。
6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。对非GET类型的请求,返回结果里面的每个JSONObject里都会有一对code和msg说明这个JSONObject的状态。下同。
7.id等字段对应的值仅供说明,不一定是数据库里存在的,请求里用的是真实存在的值。下同。
Method | URL | Request | Response |
---|---|---|---|
GET: common query |
base_url/get/ | { TableName:{ … } } {…}内为限制条件 例如获取一个id为235的Moment: { "Moment":{ "id":235 } } |
{ TableName:{ ... }, "code":200, "msg":"success" } 例如 { "Moment":{ "id":235, "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "code":200, "msg":"success" } |
HEAD: common count |
base_url/head/ | { TableName:{ … } } {…}内为限制条件 例如获取一个id为38710的User所发布的Moment总数: { "Moment":{ "userId":38710 } } |
{ TableName:{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "count":10 }, "code":200, "msg":"success" } |
GETS: safe, single, simple GET |
base_url/gets/ | 最外层加一个"tag":tag,其它同GET | 同GET |
HEADS: safe, single, simple HEAD |
base_url/heads/ | 最外层加一个"tag":tag,其它同HEAD | 同HEAD |
POST: create new data |
base_url/post/ | { TableName:{ … }, "tag":tag } {…}中id由服务端生成,不能传 例如一个id为38710的User发布一个新Moment: { "Moment":{ "userId":38710, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" } |
{ TableName:{ "code":200, "msg":"success", "id":38710 }, "code":200, "msg":"success" } 例如 { "Moment":{ "code":200, "msg":"success", "id":120 }, "code":200, "msg":"success" } |
PUT: modify values of keys |
base_url/put/ | { TableName:{ "id":id, … }, "tag":tag } {…}中id必传 例如修改id为235的Moment的content: { "Moment":{ "id":235, "content":"APIJSON,let interfaces and documents go to hell !" }, "tag":"Moment" } |
同POST |
DELETE: delete data |
base_url/delete/ | { TableName:{ "id":id }, "tag":tag } {…}中id必传,一般只传id 例如删除id为120的Moment: { "Moment":{ "id":120 }, "tag":"Moment" } |
同POST |
1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的key,一般是要查询的table的名称,由服务端Request表中指定。下同。
3.GET、HEAD请求是开放请求,可任意组合任意嵌套。其它请求为受限制的安全/私密请求,对应的 方法、tag、结构 都必须和 服务端Request表中所指定的 一一对应,否则请求将不被通过。下同。
4.GETS与GET、HEADS与HEAD分别为同一类型的操作方法,请求稍有不同但返回结果相同。下同。
5.在HTTP通信中,GET、HEAD方法一般用HTTP GET请求,其它一般用HTTP POST请求。下同。
6.所有JSONObject都视为容器(或者文件夹),结构为 {...} ,里面可以放普通对象或子容器。下同。
7.每个对象都有一个唯一的路径(或者叫地址),假设对象名为refKey,则用 key0/key1/.../refKey 表示。下同。
Function | Key-Value | Example |
---|---|---|
Array | "key[]":{},后面是JSONObject,key可省略。当key和里面的Table名相同时,Table会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | {"User[]":{"User":{}}},查询一个User数组。这里key和Table名都是User,User会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...} |
Options | "key{}":[],后面是JSONArray,作为key可取的值的选项 | "id{}":[38710,82001,70793],查询id符合38710,82001,70793中任意一个的一个User数组 |
Conditions | "key{}":"条件0,条件1...",条件为任意SQL比较表达式字符串,非Number类型必须用''包含条件的值,如'a' | "id{}":"<=80000,>90000",查询id符合id<=80000 | id>90000的一个User数组 |
Contain | "key<>":Object => "key<>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | "contactIdList<>":38710,查询contactIdList包含38710的一个User数组 |
Function | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...) | "isPraised()":"isContain(praiseUserIdList,userId)",会调用 boolean isContain(JSONObject request, String array, String value) 函数,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) |
Reference | "key@":"引用路径",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。 被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." |
"Moment":{ "userId":38710 }, "User":{ "id@":"/Moment/userId" } User内的id引用了与User同级的Moment内的userId, 即User.id = Moment.userId,请求完成后 "id@":"/Moment/userId" 会变成 "id":38710 |
Search | "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | "name$":"%m%",查询name包含"m"的一个User数组 |
RegEx | "key?":"正则表达式" => "key?":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,可用于高级搜索 | "name?":"^[0-9]+$",查询name中字符全为数字的一个User数组 |
Alias | "name:alias",name映射为alias,用alias替代name。可用于 column,Table,SQL函数 等。只用于GET类型、HEAD类型的请求 | "@column":"toId:parentId",将查询的字段toId变为parentId返回 |
Add | "key+":Object,Object的类型由key指定,且类型为Number,String,JSONArray中的一种。如 82001,"apijson",["url0","url1"] 等。只用于PUT请求 | "praiseUserIdList+":[82001],添加一个点赞用户id,即这个用户点了赞 |
Remove | "key-":Object,与"key+"相反 | "balance-":100.00,余额减少100.00,即花费了100元 |
Logic | &, |, ! 逻辑运算符 ① & 可用于"key&{}":"条件"等 ② | 可用于"key|{}":"条件", "key|{}":[]等,一般可省略 ③ ! 可单独使用,如"key!":Object,也可像&,|一样配合其他功能符使用 |
① "id&{}":">80000,<=90000",即id满足id>80000 & id<=90000 ② "id|{}":">90000,<=80000",同"id{}":">90000,<=80000",即id满足id>90000 | id<=80000 ③ "id!{}":[82001,38710],即id满足 ! (id=82001 | id=38710),可过滤黑名单的消息 |
Keywords in arrays | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定 ① "count":Integer,查询数量,假设允许查询数组的最大数量为max(默认为100),则当count在1~max范围内时,查询count个;否则查询max个 ② "page":Integer,查询页码,从0开始,一般和count一起用 ③ "query":Integer,查询内容 0-对象,1-总数,2-以上全部 总数关键词为total,和query同级,通过引用赋值得到,如 "total@":"/[]/total" 这里query及total仅为GET类型的请求提供方便,一般可直接用HEAD类型的请求获取总数 ④ "join":"&/Table0/key0@,</Table1/key1@" 多表连接方式: "<" - LEFT JOIN ">" - RIGHT JOIN "&" - INNER JOIN "|" - FULL JOIN "!" - OUTTER JOIN |
① 查询User数组,最多5个: "count":5 ② 查询第3页的User数组,每页5个: "count":5, "page":3 ③ 查询User数组和对应的User总数: "[]":{ "query":2, "User":{} }, "total@":"/[]/total" ④ Moment INNER JOIN User LEFT JOIN Comment: "[]":{ "join": "&/User/id@,</Comment/momentId@", "Moment":{}, "User":{ "name?":"t", "id@": "/Moment/userId" }, "Comment":{ "momentId@": "/Moment/id" } } |
Keywords in objects | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定 ① "@combine":"key0,&key1,|key2,!key3,...", 条件组合方式 ② "@column":"key0,key1...", 返回字段 ③ "@order":"key0,key1+,key2-...",排序方式 ④ "@group":"key0,key1,key2...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件: 1.分组的key在@column里声明 2.Table主键在@group中声明 ⑤ "@having":"function0(...)?valu0,function1(...)?valu1,function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明 ⑥ "@otherKey": Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 |
① 搜索name或tag任何一个字段包含字符a的User列表: "name?":"a", "tag?":"a", "@combine":"name?,tag?" ② 只查询id,sex,name这几列并且请求结果也按照这个顺序: "@column":"id,sex,name" ③ 查询按 name降序、id默认顺序 排序的User数组: "@order":"name-,id" ④ 查询按userId分组的Moment数组: "@group":"userId,id" ⑤ 查询 按userId分组、id最大值>=100 的Moment数组: "@column":"userId,max(id)", "@group":"userId", "@having":"max(id)>=100" 还可以指定函数返回名: "@column":"userId,max(id):maxId", "@group":"userId", "@having":"maxId>=100" ⑥ 从pictureList获取第0张图片: "@position":0, //这里@position为自定义关键词 "firstPicture()":"getFromArray(pictureList,@position)" |
Clone or download > Download ZIP > Unzip to a path and remember it.
This Server project needs MySQL Server and MySQLWorkbench. And you must ensure that both of them were installed.
My config is Windows 7 + MySQL Community Server 5.7.16 + MySQLWorkbench 6.3.7 and OSX EI Capitan + MySQL Community Server 5.7.16 + MySQLWorkbench 6.3.8. The systems and softwares are all 64 bit.
Start MySQLWorkbench > Enter a connection > Click Server menu > Data Import > Select the path of APIJSON-Master/table > Start Import > Refresh SCHEMAS, and you'll see the tables were already added.
If you haven't installed any editor above, please download and install one before run.
My config is Windows 7 + JDK 1.7.0_71 + Eclipse 4.6.1 + IntellIJ 2016.3 and OSX EI Capitan + JDK 1.8.0_91 + Eclipse 4.6.1 + IntellIJ 2016.2.5. The systems and softwares are all 64 bit.
1.Import
File > Import > Maven > Existing Maven Projects > Next > Browse > Select the path of APIJSON-Master/APIJSON(Server)/APIJSON(Eclipse_JEE) > Finish
2.Run
Run > Run As > Java Application > Select APIJSONApplication > OK
1.Import
Open > Select the path of APIJSON-Master/APIJSON(Server)/APIJSON(Idea) > OK
2.Run
Run > Run APIJSONApplication
You can skip this step and download the Client App below.
If you haven't installed any editor above, please download and install one before run.
My config is Windows 7 + JDK 1.7.0_71 + ADT Bundle 20140702 + Android Studio 2.2 and OSX EI Capitan + (JDK 1.7.0_71 + ADT Bundle 20140702) + (JDK 1.8.0_91 + Android Studio 2.1.2). The systems and softwares are all 64 bit.
1.Import
File > Import > Android > Existing Android Code Into Workspace > Next > Browse > Select the path of APIJSON-Master/APIJSON(Android)/APIJSON(ADT) > Finish
2.Run
Run > Run As > Android Application
1.Import
Open an existing Android Studio project > Select the path of APIJSON-Master/APIJSON(Android)/APIJSON(AndroidStudio) > OK
2.Run
Run > Run app
Select an APIJSON request to send to server and wait. It will show the result received.
If the default url is not available, change it to an available one, such as an IPV4 address of a computer running APIJSON Server project. Then click the Query button to request again.
TommyLemon:https://github.com/TommyLemon
If you have any questions or suggestions, you can create an issue or send me an e-mail.
If you fixed some bugs or added some functions, I would greatly appreciate it if you contribute your code.
App
APIJSONApp.apk
Test
APIJSONTest.apk