JSON Patch
1.前言
可以这么说的是,任何一种非强制性约束同时也没有“标杆”工具支持的开发风格或协议(仅靠文档是远远不够的),最终的实现上都会被程序员冠上“务实”的名头,而不管成型了多少个版本,与最初的设计有什么区别。DDD 是如此,微服务是如此,REST 也是如此。
虽然这也不难理解,风格从一开始被创造出来后,便不再属于作者了。所以仍然把你的符合以下标准
- 满足以资源形式定义定义 Uri
- 满足以 HTTP 谓词语义增删改查资源
- 符合命名要求
- ……
的“不标准” Web API 看作是 RESTful 的,也未尝不可。毕竟,谁在乎呢?
更深层次的讨论参见Why Some Web APIs Are Not RESTful and What Can Be Done About It。什么才是真正的 REST Api 并不是本文的重点(Github Rest API v3),笔者在后文讨论的具体实现,也只是符合目前流行的“RESTful”直觉设计。
2. HTTP 谓词
谓词 | 释义 | 幂等性 | 安全性 |
---|---|---|---|
HEAD | 用于获取资源的 HTTP Header 信息 | 是 | 是 |
GET | 用于检索信息 | 是 | 是 |
POST | 用于创建资源 | 否 | 否 |
PUT | 用于更新或替换完整资源或批量更新集合。对于没有 Body 的 PUT 动作,请将 Content-Length 设置为 0 |
是 | 否 |
DELETE | 用于删除资源 | 是 | 否 |
PATCH | 用于使用部分 JSON 数据更新资源信息(在一个请求里可搭载多个动作)。PATCH 是一个相对较新的 HTTP 谓词,在客户端或服务器不支持 PATCH 动作时,也可以使用 Post/Put 更新资源 | 否 | 否 |
3. PATCH & JSON Patch
结合上述 HTTP 谓词,通常情况下,更新部分资源的部分数据时,有以下四种做法:
- 使用 PUT 谓词, 尽可能使用完整对象来更新资源(即根本不使用 PATCH )。
- 使用 JSON Merge Patch 更新部分资源的部分数据(需要使用指定 MIME
application/merge-patch+json
来表示)。 - 使用 PATCH 谓词和 JSON Patch(需要使用指定 MIME
application/json-patch+json
来表示) - 如果请求不以 MIME 的语义定义的方式修改资源,使用具有合理描述的 POST 谓词。
我相信大部分系统中,采取的都是第1种和第4种做法,而本文的主题则是第3种做法。
在 RFC 5789(PATCH method for HTTP) 中,有一个关于 PATCH 请求的小例子:
PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100
[description of changes]
[description of changes]
代表对目标资源的一系列操作,而JSON Patch
则是描述操作的文档格式。
// 示例 json 文档
{
"a":{
"b":{
"c":"foo"
}
}
}
// JSON Patch 操作
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
在这个JSON Patch
的例子中,op
代表操作类型,from
和path
代表目标 json 的层级路径,value
代表操作值。相关语义想必大家都能直接读出来,更多的信息请参考What is JSON Patch?和 RFC JSON Patch。
示例应用
示例程序引入了swagger
,MongoDB
,docker-compose
等功能,关于 JsonPatch 的部分则使用微软官方的 JsonPatch 编写,该库支持add
,remove
,replace
,move
,copy
方法,实现并不困难。实际使用时,直接以JsonPatchDocument<T>
作为包装即可。
MongoDB 客户端推荐注册为单例。
public interface IMongoDatabaseProvider
{
IMongoDatabase Database { get; }
}
public class MongoDatabaseProvider : IMongoDatabaseProvider
{
private readonly IOptions<Settings> _settings;
public MongoDatabaseProvider(IOptions<Settings> settings)
{
_settings = settings;
}
public IMongoDatabase Database
{
get
{
var client = new MongoClient(_settings.Value.ConnectionString);
return client.GetDatabase(_settings.Value.Database);
}
}
}
/* Startup/ConfigureServices.cs */
public void ConfigureServices(IServiceCollection services)
{
…
services.AddSingleton<IMongoDatabaseProvider, MongoDatabaseProvider>();
…
}
appsettings.json
文件中的数据库配置部分则为:
{
"ConnectionString": "mongodb://mongodb",
"Database": "ExampleDb"
}
docker-compose.yml
对 web 应用和 MongoDB 的配置如下:
version: '3.4'
services:
aspnetcorejsonpatch:
image: aspnetcorejsonpatch
build:
context: .
dockerfile: AspNetCoreJsonPatch/Dockerfile
depends_on:
- mongodb
ports:
- "8080:80"
mongodb:
image: mongo
ports:
- "27017:27017"
启动时,定位到docker-compose.yml
所在文件夹,运行docker-compose up
,然后在浏览器访问localhost:8080/swagger
,应用在启动后会自动创建ExampleDb
数据库并插入一条数据。笔者也写了一个获取信息的接口/api/Persons
,返回值如下:
[
{
"name": "LeBron James",
"oId": "5af995a5b8ea8500018d54b7"
}
]
然后再使用返回的oId
请求/api/Persons/{id}
(UpdateThenAddThenRemoveAsync
)接口,body
的 JsonPatch 描述则用:
/* body */
[
{
"value": "Daby",
"path": "FirstName",
"op": "replace"
},
{
"value": "Example Address",
"path": "Address",
"op": "add"
},
{
"path": "Mail",
"op": "remove"
}
]
/* PersonsController.cs */
[HttpPatch("{id}")]
public async Task<PersonDto> UpdateThenAddThenRemoveAsync(string id,
[FromBody] JsonPatchDocument<Person> personPatch)
{
var objectId = new ObjectId(id);
var person = await _personRepository.GetAsync(objectId);
personPatch.ApplyTo(person);
await _personRepository.UpdateAsync(person);
return new PersonDto
{
OId = person.Id.ToString(),
Name = $"{person.FirstName} {person.LastName}"
};
}
其他相关代码另请查阅。不过需要再提一点的是,Visual Studio 15.7 版本对docker-compose.yml
的文本语法解析有些问题,详见MSBuild failing to parse a valid compose file,比如以下代码将无法编译:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${MONGODB:-mongodb://mongodb}
- Database=ExampleDb
参考文献
JSON Patch的更多相关文章
- 【ASP.NET Core】JSON Patch 使用简述
JSON Patch 是啥玩意儿?不知道,直接翻译吧,就叫它“Json 补丁”吧.干吗用的呢?当然是用来修改 JSON 文档的了.那咋修改呢?比较常见有四大操作:AMRR. 咋解释呢? A—— Add ...
- 如何在ASP.NET Core中使用JSON Patch
原文: JSON Patch With ASP.NET Core 作者:.NET Core Tutorials 译文:如何在ASP.NET Core中使用JSON Patch 地址:https://w ...
- [译] 在Web API 2 中实现带JSON的Patch请求
原文链接:The Patch Verb in Web API 2 with JSON 我想在.NET4.6 Web API 2 项目中使用Patch更新一个大对象中的某个字断,这才意识到我以前都没有用 ...
- 用ASP.NET Core 2.0 建立规范的 REST API -- DELETE, UPDATE, PATCH 和 Log
本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...
- streamsets http client && json parse && local fs 使用
streamsets 包含了丰富的组件,origin processer destination 测试例子为集成了http client 以及json 处理 启动服务 使用docker 创建pipel ...
- kubectl 之 patch 命令
patch命令 kubectl patch — Update field(s) of a resource using strategic merge patch Synopsis kubectl p ...
- Kubernetes官方java客户端之七:patch操作
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Json文件解析(下)
Json文件解析(下) 代码地址:https://github.com/nlohmann/json 从STL容器转换 任何序列容器(std::array,std::vector,std::dequ ...
- javaScript系列 [09]-javaScript和JSON (拓展)
本文输出JSON搜索和JSON转换相关的内容,是对前两篇文章的补充. JSON搜索 在特定的开发场景中,如果服务器端返回的JSON数据异常复杂(可能超过上万行),那么必然就有对JSON文档进行搜索的需 ...
随机推荐
- [Ext.Net]动态生成控件(二)--js动态添加文本框
转自:http://www.ext.net.cn/forum.php?mod=viewthread&tid=11931 点击一个按钮就出现一行控件,点击删除控件就可将一行控件删除,这是不是你一 ...
- Cocos2D v2.0至v3.x简洁转换指南(二)
触摸处理 我们在稍后将完成Cocos2d 3.0中触摸处理的完整教程.而现在最重要的是知道如何去启用触摸处理在你的CCNode中: self.userInteractionEnabled = TRUE ...
- 华为机试题【10】-求数字基root
题目描述: 求整数的Root:给定正整数,求每位数字之和;如果和不是一位数,则重复; 输入:输入任意一个或多个整数 输出:输出各位数字之和,直到和为个位数为止(输入异常,则返回-1),多行,每行对应一 ...
- 浅谈我为什么选择用Retrofit作为我的网络请求框架
比较AsyncTask.Volley.Retrofit三者的请求时间 使用 单次请求 7个请求 25个请求 AsyncTask 941ms 4539ms 13957ms Volley 560ms 22 ...
- Java泛型和通配符那点事
泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法 ...
- android 如何添加第3方lib库到kernel中
注意:只能将lib库放在kernel编译到的地方,如下: alps/kernel/ alps/mediatek/custom/common/kernel/ alps/mediatek/custom/$ ...
- IOS常见的加密方法,常用的MD5和Base64
iOS代码加密常用加密方式 iOS代码加密常用加密方式,常见的iOS代码加密常用加密方式算法包括MD5加密.AES加密.BASE64加密,三大算法iOS代码加密是如何进行加密的,且看下文 MD5 iO ...
- MOOS学习笔记4——独立线程不同回调
MOOS学习笔记4--独立线程不同回调 /** * @fn 独立线程不同回调 * @version v1.0 * @author */ #include "MOOS/libMOOS/Comm ...
- Mac环境svn的使用
在Windows环境中,我们一般使用TortoiseSVN来搭建svn环境.在Mac环境下,由于Mac自带了svn的服务器端和客户端功能,所以我们可以在不装任何第三方软件的前提下使用svn功能,不过还 ...
- JS (全局作用域)
一.全局函数作用域(把变量的声明和函数的声明放在前面) 作用域(scope):一条数据可以在哪个范围中使用. 通常来说,一段程序代码中所用到的数据并不总是有效/可用的,而限定这个数据的可用性的代码范围 ...