Skip to content

Commit

Permalink
support json schema #239
Browse files Browse the repository at this point in the history
  • Loading branch information
wenshao committed May 15, 2022
1 parent 8629b78 commit fee2ced
Show file tree
Hide file tree
Showing 4 changed files with 433 additions and 0 deletions.
36 changes: 36 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSON.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand Down Expand Up @@ -100,6 +101,21 @@ static JSONObject parseObject(String text, JSONReader.Feature... features) {
}
}

/**
* Parse UTF8 inputStream into into {@link JSONObject}
*
* @param input the JSON {@link InputStream} to be parsed
* @param features features to be enabled in parsing
* @return JSONObject
*/
static JSONObject parseObject(InputStream input, JSONReader.Feature... features) {
try (JSONReader reader = JSONReader.of(input, StandardCharsets.UTF_8)) {
reader.getContext().config(features);
ObjectReader<JSONObject> objectReader = reader.getObjectReader(JSONObject.class);
return objectReader.readObject(reader, 0);
}
}

/**
* Parse UTF8 encoded JSON byte array into {@link JSONObject}
*
Expand Down Expand Up @@ -423,6 +439,26 @@ static <T> T parseObject(InputStream input, Type type, JSONReader.Feature... fea
}
}

/**
* Parse UTF8 URL Resource into a Java object with specified {@link JSONReader.Feature}s enabled
*
* @param url the JSON {@link URL} to be parsed
* @param type specify the {@link Type} to be converted
* @param features features to be enabled in parsing
*/
@SuppressWarnings("unchecked")
static <T> T parseObject(URL url, Type type, JSONReader.Feature... features) {
if (url == null) {
return null;
}

try (InputStream is = url.openStream()){
return parseObject(is, type, features);
} catch (IOException e) {
throw new JSONException("parseObject error", e);
}
}

/**
* Parse UTF8 inputStream into a Java object with specified {@link JSONReader.Feature}s enabled
*
Expand Down
276 changes: 276 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONSchema.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
package com.alibaba.fastjson2;

import org.everit.json.schema.StringSchema;

import java.math.BigInteger;
import java.util.*;

public abstract class JSONSchema {
final String title;
final String description;

JSONSchema(JSONObject input) {
this.title = input.getString("title");
this.description = input.getString("description");
}

public static JSONSchema of(JSONObject input) {
return new ObjectSchema(input);
}

public String getTitle() {
return title;
}

public String getDescription() {
return description;
}

public abstract Type getType();

public abstract void validate(Object value);

public enum Type {
Null,
Boolean,
Object,
Array,
Number,
String,

// extended type
Integer,
}

public static final class StringSchema extends JSONSchema {
StringSchema(JSONObject input) {
super(input);
}

@Override
public Type getType() {
return Type.String;
}

@Override
public void validate(Object value) {
if (value == null) {
return;
}

if (value instanceof String) {
return;
}

throw new JSONValidException("type Integer not match : " + value.getClass().getName());
}
}

public static final class IntegerSchema extends JSONSchema {
IntegerSchema(JSONObject input) {
super(input);
}

@Override
public Type getType() {
return Type.Integer;
}

@Override
public void validate(Object value) {
if (value == null) {
return;
}

Class valueClass = value.getClass();
if (valueClass == Byte.class
|| valueClass == Short.class
|| valueClass == Integer.class
|| valueClass == Long.class
|| valueClass == BigInteger.class
) {
return;
}

throw new JSONValidException("type Integer not match : " + valueClass.getName());
}
}

public static final class NumberSchema extends JSONSchema {
NumberSchema(JSONObject input) {
super(input);
}

@Override
public Type getType() {
return Type.Number;
}

@Override
public void validate(Object value) {
if (value == null) {
return;
}

if (value instanceof Number) {
return;
}

throw new JSONValidException("type Integer not match : " + value.getClass().getName());
}
}

public static final class BooleanSchemaSchema extends JSONSchema {
BooleanSchemaSchema(JSONObject input) {
super(input);
}

@Override
public Type getType() {
return Type.Boolean;
}

@Override
public void validate(Object value) {
throw new UnsupportedOperationException();
}
}

public static final class NullSchemaSchema extends JSONSchema {
NullSchemaSchema(JSONObject input) {
super(input);
}

@Override
public Type getType() {
return Type.Null;
}

@Override
public void validate(Object value) {
throw new UnsupportedOperationException();
}
}

public static final class ArraySchemaSchema extends JSONSchema {
ArraySchemaSchema(JSONObject input) {
super(input);
}

@Override
public Type getType() {
return Type.Array;
}

@Override
public void validate(Object value) {
throw new UnsupportedOperationException();
}
}

public static final class ObjectSchema extends JSONSchema {
final JSONObject properties;
final Set<String> required;

public ObjectSchema(JSONObject input) {
super(input);
this.properties = new JSONObject();

JSONObject properties = input.getJSONObject("properties");
if (properties != null) {
for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> entry = it.next();
String entryKey = entry.getKey();
JSONObject entryValue = (JSONObject) entry.getValue();
Type type = entryValue.getObject("type", Type.class);
if (type == null) {
throw new JSONException("type required");
}

JSONSchema schema;
switch (type) {
case String:
schema = new StringSchema(entryValue);
break;
case Integer:
schema = new IntegerSchema(entryValue);
break;
case Number:
schema = new NumberSchema(entryValue);
break;
case Null:
schema = new NullSchemaSchema(entryValue);
break;
case Boolean:
schema = new BooleanSchemaSchema(entryValue);
break;
case Object:
schema = new ObjectSchema(entryValue);
break;
case Array:
schema = new ArraySchemaSchema(entryValue);
break;
default:
throw new JSONException("not support type : " + type);
}

this.properties.put(entryKey, schema);
}
}

JSONArray required = input.getJSONArray("required");
if (required == null) {
this.required = Collections.emptySet();
} else {
this.required = new LinkedHashSet<>(required.size());
for (int i = 0; i < required.size(); i++) {
this.required.add(
required.getString(i)
);
}
}
}

@Override
public Type getType() {
return Type.Object;
}

@Override
public void validate(Object value) {
if (value instanceof Map) {
Map map = (Map) value;

for (String item : required) {
if (!map.containsKey(item)) {
throw new JSONValidException("require property '" + item + "'");
}
}

for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
JSONSchema schema = (JSONSchema) entry.getValue();

Object propertyValue = map.get(key);
schema.validate(propertyValue);
}

return;
}

throw new UnsupportedOperationException();
}

public Map<String, JSONSchema> getProperties() {
return properties;
}

public JSONSchema getProperty(String key) {
return (JSONSchema) properties.get(key);
}

public Set<String> getRequired() {
return required;
}
}
}
Loading

0 comments on commit fee2ced

Please sign in to comment.