Java 如何验证 REST 服务中的传入 JSON 数据?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18154983/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I validate incoming JSON data inside a REST service?
提问by My-Name-Is
A rest service needs to validate all incoming json data against a json schema. The json schemas are public accessible and can be retrieved via http requests.
休息服务需要根据 json 模式验证所有传入的 json 数据。json 模式可公开访问,并可通过 http 请求检索。
I'm using the Hymanson-framwork for marshaling and unmarshaling between java and json. So far I couldn't find any possibility to validate the data against the schema by using Hymanson.
我正在使用 Hymanson-framework 在 java 和 json 之间进行编组和解组。到目前为止,我找不到任何可能通过使用 Hymanson 来根据模式验证数据。
I also tried the JsonToolsframework which obviously comes up with such a validation functionality. But unfortunately it wasn't possible for me to get the validation to work. Why JsonTool schema validation isn't working?
我还尝试了JsonTools框架,它显然提供了这样的验证功能。但不幸的是,我无法进行验证。为什么 JsonTool 架构验证不起作用?
How can I do such a validation?
我怎样才能做这样的验证?
采纳答案by My-Name-Is
I searched for the best practice to enforce validation for incoming json data into a RESTful service. My suggestion is to use a MessageBodyReader
which performs the validation inside the readFrom
method. Below there is an message-body-reader example which is non-generic for the sake of simplicity.
我搜索了将传入 json 数据强制验证到 RESTful 服务的最佳实践。我的建议是使用 aMessageBodyReader
在readFrom
方法内部执行验证。下面是一个 message-body-reader 示例,为了简单起见,它是非通用的。
I also was interesed in finding the best framework for doing json data validation. Because I use the Hymanson framework (version 1.8.5) for marshaling and unmarshaling between json and java, it would have been nice if this framework would provide a json data validation functionality. Unfortunately I couldn't find any possibility to do this with Hymanson. Finally I got it working with the json-schema-validatoravailable at https://github.com. The version I use is 2.1.7
我也有兴趣找到进行 json 数据验证的最佳框架。因为我使用 Hymanson 框架(版本 1.8.5)在 json 和 java 之间进行编组和解组,如果这个框架能够提供 json 数据验证功能就太好了。不幸的是,我找不到任何可能用Hyman逊来做到这一点。最后,我让它与https://github.com 上可用的json-schema-validator一起工作。我用的版本是2.1.7
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.codehaus.Hymanson.map.ObjectMapper;
import at.fhj.ase.dao.data.Address;
import at.fhj.ase.xmlvalidation.msbreader.MessageBodyReaderValidationException;
import com.fasterxml.Hymanson.databind.JsonNode;
import com.github.fge.Hymanson.JsonLoader;
import com.github.fge.jsonschema.exceptions.ProcessingException;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.github.fge.jsonschema.main.JsonValidator;
import com.github.fge.jsonschema.report.ProcessingReport;
@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class AddressJsonValidationReader implements MessageBodyReader<Address> {
private final String jsonSchemaFileAsString;
public AddressJsonValidationReader(@Context ServletContext servletContext) {
this.jsonSchemaFileAsString = servletContext
.getRealPath("/json/Address.json");
}
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
if (type == Address.class) {
return true;
}
return false;
}
@Override
public Address readFrom(Class<Address> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
final String jsonData = getStringFromInputStream(entityStream);
System.out.println(jsonData);
InputStream isSchema = new FileInputStream(jsonSchemaFileAsString);
String jsonSchema = getStringFromInputStream(isSchema);
/*
* Perform JSON data validation against schema
*/
validateJsonData(jsonSchema, jsonData);
/*
* Convert stream to data entity
*/
ObjectMapper m = new ObjectMapper();
Address addr = m.readValue(stringToStream(jsonData), Address.class);
return addr;
}
/**
* Validate the given JSON data against the given JSON schema
*
* @param jsonSchema
* as String
* @param jsonData
* as String
* @throws MessageBodyReaderValidationException
* in case of an error during validation process
*/
private void validateJsonData(final String jsonSchema, final String jsonData)
throws MessageBodyReaderValidationException {
try {
final JsonNode d = JsonLoader.fromString(jsonData);
final JsonNode s = JsonLoader.fromString(jsonSchema);
final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
JsonValidator v = factory.getValidator();
ProcessingReport report = v.validate(s, d);
System.out.println(report);
if (!report.toString().contains("success")) {
throw new MessageBodyReaderValidationException(
report.toString());
}
} catch (IOException e) {
throw new MessageBodyReaderValidationException(
"Failed to validate json data", e);
} catch (ProcessingException e) {
throw new MessageBodyReaderValidationException(
"Failed to validate json data", e);
}
}
/**
* Taken from <a href=
* "http://www.mkyong.com/java/how-to-convert-inputstream-to-string-in-java/"
* >www.mkyong.com</a>
*
* @param is
* {@link InputStream}
* @return Stream content as String
*/
private String getStringFromInputStream(InputStream is) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try {
br = new BufferedReader(new InputStreamReader(is));
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
private InputStream stringToStream(final String str) throws UnsupportedEncodingException {
return new ByteArrayInputStream(str.getBytes("UTF-8"));
}
}
回答by Premraj
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.github.fge.Hymanson.JsonLoader;
import com.fasterxml.Hymanson.databind.JsonNode;
public class ValidationJSON {
public static void main(String[] arr){
String jsonData = "{\"name\": \"prem\"}";
String jsonSchema = ""; //Schema we can generate online using http://jsonschema.net/
final JsonNode data = JsonLoader.fromString(jsonData);
final JsonNode schema = JsonLoader.fromString(jsonSchema);
final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
JsonValidator validator = factory.getValidator();
ProcessingReport report = validator.validate(schema, data);
System.out.println(report.isSuccess());
}
}
回答by Vadim Samokhin
It looks like you're not tied to JSONSchema, though it seems to be your default choice. Tastes differ, but oftentimes it looks more complicated then it could. Besides, personally, I'd love to have both data and validation rules in the same place. And custom validators arguably seem to fit more naturally when used within java code instead of any sort of configuration files.
看起来您并未绑定到 JSONSchema,尽管它似乎是您的默认选择。口味不同,但通常看起来更复杂。此外,就个人而言,我希望在同一个地方同时拥有数据和验证规则。当在 java 代码而不是任何类型的配置文件中使用时,自定义验证器似乎更适合自然。
Here is how this approach looks like. Say, you have the following json object representing some payment (be it a request or response), but consisting only of discount
block for brevity:
这是这种方法的样子。假设您有以下 json 对象表示某种付款(无论是请求还是响应),但discount
为简洁起见,仅由块组成:
{
"discount":{
"valid_until":"2032-05-04 00:00:00+07",
"promo_code":"VASYA1988"
}
}
Here is what a validation code looks like:
这是验证代码的样子:
/*1 */ public class ValidatedJsonObjectRepresentingRequestOrResponse implements Validatable<JsonObjectRepresentingRequestOrResponse>
{
private String jsonString;
private Connection dbConnection;
/*6 */ public ValidatedJsonObjectRepresentingRequestOrResponse(String jsonString, Connection dbConnection)
{
this.jsonString = jsonString;
this.dbConnection = dbConnection;
}
@Override
/*13*/ public Result<JsonObjectRepresentingRequestOrResponse> result() throws Exception
{
return
/*16*/ new FastFail<>(
/*17*/ new WellFormedJson(
/*18*/ new Unnamed<>(Either.right(new Present<>(this.jsonRequestString)))
/*19*/ ),
/*20*/ requestJsonObject ->
/*21*/ new UnnamedBlocOfNameds<>(
List.of(
/*23*/ new FastFail<>(
/*24*/ new IsJsonObject(
/*25*/ new Required(
/*26*/ new IndexedValue("discount", requestJsonObject)
)
),
/*29*/ discountObject ->
/*30*/ new NamedBlocOfNameds<>(
/*31*/ "discount",
/*32*/ List.of(
/*33*/ new PromoCodeIsNotExpired(
/*34*/ new AsString(
/*35*/ new Required(
/*36*/ new IndexedValue("valid_until", discountObject)
)
)
),
/*40*/ new PromoCodeIsNotAlreadyRedeemed(
/*41*/ new PromoCodeContainsBothLettersAndDigits(
/*42*/ new Required(
/*43*/ new IndexedValue("promo_code", discountObject)
)
),
/*46*/ this.dbConnection
)
),
/*49*/ Discount.class
)
)
),
/*53*/ JsonObjectRepresentingRequestOrResponse.class
)
)
.result();
}
}
Let's see what's going on here, line by line:
让我们一行一行地看看这里发生了什么:
Line 1
Declaration of ValidatedJsonObjectRepresentingRequestOrResponse
.Line 6
Its constructor accepts raw json string. It might be either an incoming request or received response, or pretty much anything else.Line 13
: Validation starts when this method is invoked.Lines 16
: The higher-level validation object is FastFail
block. If the first argument is invalid, an error is returned right away.Lines 17-19
: json is checked whether it's well-formed or not. If the latter, validation fails fast and returns a corresponding error.Line 20
: if json is well-formed, a closure is invoked, and json data is passed as its single argument.Line 21
: json data is validated. Its structure is an unnamed block of named blocks. It corresponds to a JSON Object.Line 26
: The first (and the only) block is called discount
.Line 25
: It's required.Line 24
: It must be a json object.Line 23
: If not, an error will be returned right away because it's a FailFast
object.Line 29
: Otherwise, a closure is invoked.Line 30
: Discount
block is a named block consisting of other named entries – objects or scalars.Line 36
: The first one is called valid_until
Line 35
: It's required.Line 34
: And represented as a string, if it's really a string. If not, an error will be returned.Line 33
: Finally, check that it's not expired.Line 43
: Second parameter is called promo_code
.Line 42
: It's required as well.Line 41
: It must contain both letters and digits.Line 40
: And it should not be already redeemed. This fact is certainly persisted in our database, hence …Line 46
: … this.dbConnection
parameter.Line 49
: If all previous validation checks are successful, an object of class Discount
is created.Line 53
: Finally, JsonObjectRepresentingRequestOrResponse
is created and returned.
Line 1
的声明ValidatedJsonObjectRepresentingRequestOrResponse
。Line 6
它的构造函数接受原始 json 字符串。它可能是传入的请求或收到的响应,也可能是其他任何东西。Line 13
:调用此方法时开始验证。Lines 16
:更高级别的验证对象是FastFail
块。如果第一个参数无效,则立即返回错误。Lines 17-19
: 检查 json 是否格式正确。如果是后者,验证会很快失败并返回相应的错误。Line 20
: 如果 json 格式正确,则调用闭包,并将 json 数据作为其单个参数传递。Line 21
: json 数据经过验证。它的结构是一个命名块的未命名块。它对应于一个 JSON 对象。Line 26
:第一个(也是唯一的)块被调用discount
。Line 25
: 必须的。Line 24
: 它必须是一个json对象。Line 23
: 如果不是,会立即返回错误,因为它是一个FailFast
对象。Line 29
: 否则,调用一个闭包。Line 30
:Discount
块是由其他命名条目组成的命名块 - 对象或标量。Line 36
: 第一个被称为valid_until
Line 35
: 它是必需的。Line 34
: 并表示为一个字符串,如果它真的是一个字符串。如果没有,将返回错误。Line 33
: 最后,检查它没有过期。Line 43
: 第二个参数被调用promo_code
。Line 42
: 也是必须的。Line 41
: 它必须同时包含字母和数字。Line 40
: 它不应该已经被赎回。这个事实肯定会保留在我们的数据库中,因此... Line 46
: ...this.dbConnection
参数。Line 49
:如果之前的所有验证检查都成功,Discount
则创建一个类对象。Line 53
: 最后JsonObjectRepresentingRequestOrResponse
是创建并返回。
Here is how a calling code looks when validation is successful:
以下是验证成功时调用代码的外观:
Result<JsonObjectRepresentingRequestOrResponse> result = new ValidatedJsonObjectRepresentingRequestOrResponse(jsonRequestString).result();
result.isSuccessful();
result.value().raw().discount().promoCode(); // VASYA1988
This example is taken from here. Here you can find a full-fledged json request validation example.
这个例子取自这里。在这里您可以找到一个完整的json 请求验证示例。