From eeb5f9aea10b3d92e45980dd50976fe689c01571 Mon Sep 17 00:00:00 2001 From: StevenRS11 Date: Fri, 20 Jun 2014 14:04:07 -0400 Subject: [PATCH] added json validation for saves --- .../saving/DimDataProcessor.java | 311 ++-------- .../mod_pocketDim/util/JSONValidator.java | 544 ++++++++++++++++++ .../assets/dimdoors/text/Dim_Data_Schema.json | 229 ++++++++ 3 files changed, 813 insertions(+), 271 deletions(-) create mode 100644 src/main/java/StevenDimDoors/mod_pocketDim/util/JSONValidator.java create mode 100644 src/main/resources/assets/dimdoors/text/Dim_Data_Schema.json diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java b/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java index b8aae24..c30717e 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java @@ -1,37 +1,44 @@ package StevenDimDoors.mod_pocketDim.saving; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; - +import StevenDimDoors.mod_pocketDim.Point3D; +import StevenDimDoors.mod_pocketDim.util.BaseConfigurationProcessor; +import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException; +import StevenDimDoors.mod_pocketDim.util.JSONValidator; +import StevenDimDoors.mod_pocketDim.util.Point4D; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; -import StevenDimDoors.mod_pocketDim.Point3D; -import StevenDimDoors.mod_pocketDim.core.DDLock; -import StevenDimDoors.mod_pocketDim.util.BaseConfigurationProcessor; -import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException; -import StevenDimDoors.mod_pocketDim.util.Point4D; - public class DimDataProcessor extends BaseConfigurationProcessor { + private static final String JSON_SCHEMA_PATH = "/assets/dimdoors/text/Dim_Data_Schema.json"; + private static final JsonParser jsonParser = new JsonParser(); + @Override public PackedDimData readFromStream(InputStream inputStream) throws ConfigurationProcessingException { try { + //read in the json save file represeting a single dimension JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8")); - PackedDimData data = this.createDImDataFromJson(reader); + PackedDimData data = this.readDimDataJson(reader); reader.close(); return data; + } - catch (IOException e) + catch (Exception e) { e.printStackTrace(); throw new ConfigurationProcessingException("Could not read packedDimData"); @@ -43,284 +50,46 @@ public class DimDataProcessor extends BaseConfigurationProcessor public void writeToStream(OutputStream outputStream, PackedDimData data) throws ConfigurationProcessingException { - /** Print out dimData using the GSON built in serializer. I dont feel bad doing this because - * 1- We can read it - * 2- We are manually reading the data in. - * 3- The error messages tell us exactly where its failing, so its easy to fix - */ - + //create a json object from a packedDimData instance GsonBuilder gsonBuilder = new GsonBuilder(); Gson gson = gsonBuilder.setPrettyPrinting().create(); + JsonElement ele = gson.toJsonTree(data); try { - outputStream.write(gson.toJson(data).getBytes("UTF-8")); + //ensure our json object corresponds to our schema + validateJson(ele); + outputStream.write(data.toString().getBytes("UTF-8")); outputStream.close(); } - catch (IOException e) + catch (Exception e) { // not sure if this is kosher, we need it to explode, but not by throwing the IO exception. throw new ConfigurationProcessingException("Incorrectly formatted save data"); } } + + public PackedDimData readDimDataJson(JsonReader reader) throws IOException + { + JsonElement ele = jsonParser.parse(reader); + this.validateJson(ele); + GsonBuilder gsonBuilder = new GsonBuilder(); + return gsonBuilder.create().fromJson(ele, PackedDimData.class); + } + /** - * Nightmare method that takes a JsonReader pointed at a serialized instance of PackedDimData - * @param reader + * checks our json against the dim data schema + * @param data * @return * @throws IOException */ - public PackedDimData createDImDataFromJson(JsonReader reader) throws IOException + public boolean validateJson(JsonElement data) throws IOException { - int ID; - boolean IsDungeon; - boolean IsFilled; - int Depth; - int PackDepth; - int ParentID; - int RootID; - PackedDungeonData Dungeon = null; - Point3D Origin; - int Orientation; - List ChildIDs; - List Links; - List Tails = new ArrayList(); - - reader.beginObject(); - - reader.nextName(); - if (reader.nextLong() != PackedDimData.SAVE_DATA_VERSION_ID) - { - throw new IOException("Save data version mismatch"); - } - - reader.nextName(); - ID = reader.nextInt(); - - reader.nextName(); - IsDungeon = reader.nextBoolean(); - - reader.nextName(); - IsFilled = reader.nextBoolean(); - - reader.nextName(); - Depth = reader.nextInt(); - - reader.nextName(); - PackDepth = reader.nextInt(); - - reader.nextName(); - ParentID=reader.nextInt(); - - reader.nextName(); - RootID= reader.nextInt(); - - if(reader.nextName().equals("DungeonData")) - { - Dungeon = createDungeonDataFromJson(reader); - reader.nextName(); - } - - Origin = createPointFromJson(reader); - - reader.nextName(); - Orientation = reader.nextInt(); - - reader.nextName(); - ChildIDs = this.createIntListFromJson(reader); - - reader.nextName(); - Links = this.createLinksListFromJson(reader); - - return new PackedDimData(ID, Depth, PackDepth, ParentID, RootID, Orientation, IsDungeon, IsFilled, Dungeon, Origin, ChildIDs, Links, Tails); + InputStream in = this.getClass().getResourceAsStream(JSON_SCHEMA_PATH); + JsonReader reader = new JsonReader(new InputStreamReader(in)); + JSONValidator.validate((JsonObject) jsonParser.parse(reader), data); + reader.close(); + in.close(); + return true; } - - private Point3D createPointFromJson(JsonReader reader) throws IOException - { - reader.beginObject(); - - reader.nextName(); - int x = reader.nextInt(); - - reader.nextName(); - int y = reader.nextInt(); - - reader.nextName(); - int z = reader.nextInt(); - - reader.endObject(); - - return new Point3D(x,y,z); - } - - private Point4D createPoint4DFromJson(JsonReader reader) throws IOException - { - reader.beginObject(); - - reader.nextName(); - int x = reader.nextInt(); - - reader.nextName(); - int y = reader.nextInt(); - - reader.nextName(); - int z = reader.nextInt(); - - reader.nextName(); - int dimension = reader.nextInt(); - - reader.endObject(); - - return new Point4D(x,y,z,dimension); - } - - private List createIntListFromJson(JsonReader reader) throws IOException - { - List list = new ArrayList(); - reader.beginArray(); - - while (reader.peek() != JsonToken.END_ARRAY) - { - list.add(reader.nextInt()); - - } - reader.endArray(); - return list; - } - - private List createLinksListFromJson(JsonReader reader) throws IOException - { - List list = new ArrayList(); - - reader.beginArray(); - - while (reader.peek() != JsonToken.END_ARRAY) - { - list.add(createLinkDataFromJson(reader)); - } - reader.endArray(); - return list; - } - - private PackedLinkData createLinkDataFromJson(JsonReader reader) throws IOException - { - DDLock lock = null; - - Point4D source; - Point3D parent; - PackedLinkTail tail; - int orientation; - List children = new ArrayList(); - - reader.beginObject(); - - reader.nextName(); - source = this.createPoint4DFromJson(reader); - - reader.nextName(); - parent = this.createPointFromJson(reader); - - reader.nextName(); - tail = this.createLinkTailFromJson(reader); - - reader.nextName(); - orientation = reader.nextInt(); - - reader.nextName(); - reader.beginArray(); - - while (reader.peek() != JsonToken.END_ARRAY) - { - children.add(this.createPointFromJson(reader)); - } - reader.endArray(); - - if(reader.peek()== JsonToken.NAME) - { - lock = this.createLockFromJson(reader); - } - reader.endObject(); - - return new PackedLinkData(source, parent, tail, orientation, children, lock); - } - private PackedDungeonData createDungeonDataFromJson(JsonReader reader) throws IOException - { - int Weight; - boolean IsOpen; - boolean IsInternal; - String SchematicPath; - String SchematicName; - String DungeonTypeName; - String DungeonPackName; - - reader.beginObject(); - @SuppressWarnings("unused") - JsonToken test = reader.peek(); - - if(reader.peek() == JsonToken.END_OBJECT) - { - return null; - } - - reader.nextName(); - Weight=reader.nextInt(); - - reader.nextName(); - IsOpen=reader.nextBoolean(); - - reader.nextName(); - IsInternal=reader.nextBoolean(); - - reader.nextName(); - SchematicPath=reader.nextString(); - - reader.nextName(); - SchematicName=reader.nextString(); - - reader.nextName(); - DungeonTypeName=reader.nextString(); - - reader.nextName(); - DungeonPackName=reader.nextString(); - - reader.endObject(); - return new PackedDungeonData(Weight, IsOpen, IsInternal, SchematicPath, SchematicName, DungeonTypeName, DungeonPackName); - } - private PackedLinkTail createLinkTailFromJson(JsonReader reader) throws IOException - { - Point4D destination = null; - int linkType; - reader.beginObject(); - reader.nextName(); - - @SuppressWarnings("unused") - JsonToken test = reader.peek(); - if (reader.peek() == JsonToken.BEGIN_OBJECT) - { - destination = this.createPoint4DFromJson(reader); - reader.nextName(); - } - - linkType = reader.nextInt(); - - reader.endObject(); - - return new PackedLinkTail(destination, linkType); - } - - private DDLock createLockFromJson(JsonReader reader) throws IOException - { - reader.nextName(); - - reader.beginObject(); - reader.nextName(); - - boolean locked = reader.nextBoolean(); - reader.nextName(); - - int key = reader.nextInt(); - reader.endObject(); - - return new DDLock(locked, key); - } - } diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/util/JSONValidator.java b/src/main/java/StevenDimDoors/mod_pocketDim/util/JSONValidator.java new file mode 100644 index 0000000..449db41 --- /dev/null +++ b/src/main/java/StevenDimDoors/mod_pocketDim/util/JSONValidator.java @@ -0,0 +1,544 @@ +package StevenDimDoors.mod_pocketDim.util; + +import static java.util.Collections.singleton; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; + +public class JSONValidator +{ + + static final public String TYPE = "type"; + static final public String ANY = "any"; + static final public String PROPERTIES = "properties"; + static final public String OPTIONAL = "optional"; + static final public String ADDITIONAL_PROPERTIES = "additionalProperties"; + static final public String MIN_LENGTH = "minLength"; + static final public String MAX_LENGTH = "maxLength"; + static final public String MINIMUM = "minimum"; + static final public String MAXIMUM = "maximum"; + static final public String PATTERN = "pattern"; + static final public String ITEMS = "items"; + static final public String ENUM = "enum"; + static final public String REQUIRED = "required"; + + + JsonObject schema; + + public JsonObject getSchema() + { + return schema; + } + + public JSONValidator(JsonObject schema) + { + this.schema = schema; + } + + static class WrongType extends JsonParseException + { + /** + * + */ + private static final long serialVersionUID = 1L; + + WrongType(String msg) + { + super(msg); + } + + static WrongType generate(String path, Set types, Type found) + { + boolean first = true; + String typeList = "'unknown'"; + for (Type type : types) + { + if (first) + { + typeList = "'" + type.getTypeString() + "'"; + first = false; + } + else + { + typeList += " or '" + type.getTypeString() + "'"; + } + } + + return new WrongType("Invalid: Expected type " + typeList + " at '" + path + "', but " + "found type '" + found.getTypeString() + "'"); + } + } + + static enum Type + { + STRING("string"), NUMBER("number"), INTEGER("integer"), BOOLEAN("boolean"), OBJECT("object"), ARRAY("array"), NULL("null"); + + String typeString; + + Type(String typeString) + { + this.typeString = typeString; + } + + public String getTypeString() + { + return typeString; + } + } + + static Set anyTypeSet() + { + HashSet hashSet = new HashSet(); + hashSet.add(Type.STRING); + hashSet.add(Type.NUMBER); + hashSet.add(Type.INTEGER); + hashSet.add(Type.BOOLEAN); + hashSet.add(Type.OBJECT); + hashSet.add(Type.ARRAY); + hashSet.add(Type.NULL); + return hashSet; + } + + static Set getSimpleType(String path, String type) + { + for (Type t : Type.values()) + { + if (t.getTypeString().equals(type)) + { + if (t != Type.NUMBER) + { + return singleton(t); + } + else + { + HashSet set = new HashSet(); + set.add(Type.NUMBER); + set.add(Type.INTEGER); + return set; + } + } + } + + if (ANY.equals(type)) + { + return anyTypeSet(); + } + + // Unknown type, spec says to allow any. + return anyTypeSet(); + } + + static Set getTypeSet(String path, JsonObject schema) throws JsonParseException + { + JsonElement typeElement = schema.get(TYPE); + + if (typeElement == null) + { + // Spec says that a missing type object means accept any type. + return anyTypeSet(); + } + + if (typeElement.isJsonPrimitive()) + { + JsonPrimitive primitive = typeElement.getAsJsonPrimitive(); + if (primitive.isString()) + { + return getSimpleType(path, primitive.getAsString()); + } + } + + if (typeElement.isJsonArray()) + { + HashSet set = new HashSet(); + JsonArray array = typeElement.getAsJsonArray(); + for (JsonElement element : array) + { + if (element.isJsonPrimitive()) + { + JsonPrimitive primitive = element.getAsJsonPrimitive(); + if (primitive.isString()) + { + set.addAll(getSimpleType(path, primitive.getAsString())); + } + } + + // Unknown type. Accept all. + return anyTypeSet(); + } + } + + // Don't know what this is, assume any. + return anyTypeSet(); + } + + static Type getType(JsonElement element) + { + if (element.isJsonArray()) + { + return Type.ARRAY; + } + if (element.isJsonObject()) + { + return Type.OBJECT; + } + if (element.isJsonNull()) + { + return Type.NULL; + } + JsonPrimitive primitive = element.getAsJsonPrimitive(); + if (primitive.isString()) + { + return Type.STRING; + } + if (primitive.isBoolean()) + { + return Type.BOOLEAN; + } + if (primitive.isNumber()) + { + BigDecimal decimal = primitive.getAsBigDecimal(); + int scale = decimal.scale(); + if (scale > 0) + { + return Type.NUMBER; + } + else + { + return Type.INTEGER; + } + } + + // Don't know. Punt and call it a string. + return Type.STRING; + } + + static void validateObject(String path, JsonObject schema, JsonObject obj) throws JsonParseException + { + Set propertiesSeen = new HashSet(); + + JsonArray required = schema.getAsJsonArray(REQUIRED); + JsonObject properties = schema.getAsJsonObject(PROPERTIES); + + if (properties == null) + { + return; + } + + Set> propertySet = properties.entrySet(); + ArrayList requiredFields = new ArrayList(); + + for(JsonElement st : required.getAsJsonArray()) + { + requiredFields.add(st.getAsString()); + } + + for (Map.Entry property : propertySet) + { + String name = property.getKey(); + String newPath = path + "['" + name + "']"; + JsonElement element = property.getValue(); + propertiesSeen.add(name); + + if (!element.isJsonObject()) + { + throw new JsonParseException("Bad Schema: property definition not an object at '" + newPath + "'"); + } + + JsonObject definition = element.getAsJsonObject(); + + JsonElement newTarget = obj.get(name); + + if (newTarget == null) + { + JsonPrimitive optional = definition.getAsJsonPrimitive(OPTIONAL); + boolean needed = ((optional==null) && requiredFields.contains(name)) || (optional != null && !optional.getAsBoolean()); + + if (needed) + { + throw new JsonParseException("Invalid: Required property '" + newPath + "' not found"); + } + } + else + { + validate(newPath, definition, newTarget); + } + } + + JsonElement additionalProperties = schema.get(ADDITIONAL_PROPERTIES); + JsonObject additionalSchema = null; + if (additionalProperties == null) + { + additionalSchema = new JsonObject(); + } + else + { + if (additionalProperties.isJsonObject()) + { + additionalSchema = additionalProperties.getAsJsonObject(); + } + } + + /* + * if (additionalSchema == null) { + * logger.debug("No additional schema for '"+path+"'"); } else { + * logger.debug("Additional schema for '"+path+"': "+ + * additionalSchema.toString()); } + */ + + Set> objectProperties = obj.entrySet(); + for (Map.Entry property : objectProperties) + { + String name = property.getKey(); + String newPath = path + "['" + name + "']"; + if (!propertiesSeen.contains(name)) + { + if (additionalSchema == null) + { + throw new JsonParseException("Invalid: Found additional property '" + newPath + "'"); + } + validate(newPath, additionalSchema, property.getValue()); + } + } + } + + static Integer getInt(String path, String attributeName, JsonObject schema) throws JsonParseException + { + JsonElement attributeElement = schema.get(attributeName); + + if (attributeElement == null) + { + return null; + } + + if (!attributeElement.isJsonPrimitive()) + { + throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not an integer at '" + path + "'"); + } + JsonPrimitive attributePrimitive = attributeElement.getAsJsonPrimitive(); + if (!attributePrimitive.isNumber()) + { + throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not an integer at '" + path + "'"); + } + + return attributePrimitive.getAsInt(); + } + + static String getString(String path, String attributeName, JsonObject schema) throws JsonParseException + { + JsonElement attributeElement = schema.get(attributeName); + + if (attributeElement == null) + { + return null; + } + + if (!attributeElement.isJsonPrimitive()) + { + throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a string at '" + path + "'"); + } + JsonPrimitive attributePrimitive = attributeElement.getAsJsonPrimitive(); + if (!attributePrimitive.isString()) + { + throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a string at '" + path + "'"); + } + + return attributePrimitive.getAsString(); + } + + static BigDecimal getBigDecimal(String path, String attributeName, JsonObject schema) throws JsonParseException + { + JsonElement attributeElement = schema.get(attributeName); + + if (attributeElement == null) + { + return null; + } + + if (!attributeElement.isJsonPrimitive()) + { + throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a number at '" + path + "'"); + } + JsonPrimitive attributePrimitive = attributeElement.getAsJsonPrimitive(); + if (!attributePrimitive.isNumber()) + { + throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a number at '" + path + "'"); + } + + return attributePrimitive.getAsBigDecimal(); + } + + static void validateString(String path, JsonObject schema, String str) throws JsonParseException + { + Integer minLength = getInt(path, MIN_LENGTH, schema); + Integer maxLength = getInt(path, MAX_LENGTH, schema); + + if ((minLength != null) && (str.length() < minLength)) + { + throw new JsonParseException("Invalid: String '" + path + "' is too short. The string needs to be more than " + minLength + " characters"); + } + + if ((maxLength != null) && (str.length() > maxLength)) + { + throw new JsonParseException("Invalid: String '" + path + "' is too long. The string needs to be less than " + maxLength + " characters"); + } + + String pattern = getString(path, PATTERN, schema); + if ((pattern != null) && (!str.matches(pattern))) + { + throw new JsonParseException("Invalid: String '" + path + "' does not match pattern '" + pattern + "'"); + } + } + + static void validateTuple(String path, JsonArray tupleSchema, JsonObject additionalSchema, JsonArray array) throws JsonParseException + { + return; + } + + static void validateArray(String path, JsonObject schema, JsonArray array) throws JsonParseException + { + JsonElement additionalProperties = schema.get(ADDITIONAL_PROPERTIES); + JsonObject additionalSchema = null; + if (additionalProperties == null) + { + additionalSchema = new JsonObject(); + } + else + { + if (additionalProperties.isJsonObject()) + { + additionalSchema = additionalProperties.getAsJsonObject(); + } + } + + JsonElement itemsElement = schema.get(ITEMS); + if (itemsElement == null) + { + return; + } + + if (itemsElement.isJsonArray()) + { + validateTuple(path, itemsElement.getAsJsonArray(), additionalSchema, array); + return; + } + + JsonObject itemsSchema = null; + if (itemsElement.isJsonObject()) + { + itemsSchema = itemsElement.getAsJsonObject(); + } + else + { + // Bogus items parameter, assume everything is valid. + itemsSchema = new JsonObject(); + } + + int i = 0; + for (JsonElement element : array) + { + ++i; + String curPath = path + "[" + i + "]"; + validate(curPath, itemsSchema, element); + } + } + + static void validateEnum(String path, JsonObject schema, JsonElement element) throws JsonParseException + { + JsonElement enumElement = schema.get(ENUM); + if (enumElement == null) + { + return; + } + + if (!enumElement.isJsonArray()) + {} + + JsonArray enumArray = enumElement.getAsJsonArray(); + + for (JsonElement curElement : enumArray) + { + if (element.equals(curElement)) + { + // We found a valid value. + return; + } + } + + throw new JsonParseException("Invalid: Property '" + path + "' is not one of the enum values."); + } + + static void validateNumber(String path, JsonObject schema, BigDecimal number) throws JsonParseException + { + BigDecimal minimum = getBigDecimal(path, MINIMUM, schema); + if (minimum != null) + { + if (number.compareTo(minimum) < 0) + { + throw new JsonParseException("Invalid: Property '" + path + "' has a value of '" + number + "' which is less than the minimum of '" + minimum + + "'."); + } + } + + BigDecimal maximum = getBigDecimal(path, MAXIMUM, schema); + if (maximum != null) + { + if (number.compareTo(maximum) > 0) + { + throw new JsonParseException("Invalid: Property '" + path + "' has a value of '" + number + "' which is greater than the maximum of '" + + maximum + "'."); + } + } + } + + static void validate(String path, JsonObject schema, JsonElement element) throws JsonParseException + { + Set typeSet = getTypeSet(path, schema); + + Type type = getType(element); + if (!typeSet.contains(type)) + { + throw WrongType.generate(path, typeSet, type); + } + + switch (type) + { + case BOOLEAN: + case NULL: + break; + case NUMBER: + case INTEGER: + validateNumber(path, schema, element.getAsBigDecimal()); + break; + case ARRAY: + validateArray(path, schema, element.getAsJsonArray()); + break; + case STRING: + validateString(path, schema, element.getAsString()); + break; + case OBJECT: + validateObject(path, schema, element.getAsJsonObject()); + break; + default: + // Unknown type + throw new JsonParseException("Internal Error"); + } + + validateEnum(path, schema, element); + } + + static public void validate(JsonObject schema, JsonElement element) throws JsonParseException + { + validate("$", schema, element); + } + + public void validate(JsonElement element) throws JsonParseException + { + validate(getSchema(), element); + } +} diff --git a/src/main/resources/assets/dimdoors/text/Dim_Data_Schema.json b/src/main/resources/assets/dimdoors/text/Dim_Data_Schema.json new file mode 100644 index 0000000..997b6dc --- /dev/null +++ b/src/main/resources/assets/dimdoors/text/Dim_Data_Schema.json @@ -0,0 +1,229 @@ +{ + "type":"object", + "$schema": "http://json-schema.org/draft-04/schema", + + "description": "A serialized Dim Data object", + "properties":{ + "ChildIDs": { + "type":"array", + "items": { + "type": "number" + } + }, + "Depth": { + "type":"number" + }, + "ID": { + "type":"number" + }, + "IsDungeon": { + "type":"boolean" + }, + "IsFilled": { + "type":"boolean" + }, + "DungeonData": { + "type": "object", + + "properties": { + "Weight": { + "type": "number" + }, + "IsOpen": { + "type": "boolean" + }, + "IsInternal": { + "type": "boolean" + }, + "SchematicPath": { + "type": "string" + }, + "SchematicName": { + "type": "string" + }, + "DungeonTypeName": { + "type": "string" + }, + "DungeonPackName": { + "type": "string" + } + }, + "required": [ + "Weight", + "IsOpen", + "IsInternal", + "SchematicPath", + "SchematicName", + "DungeonTypeName", + "DungeonPackName" + ] + }, + "Links": { + "type":"array", + "items": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items":{ + "type": "number" + } + }, + "orientation": { + "type": "number" + }, + "source": { + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + }, + "z": { + "type": "integer" + }, + "dimension": { + "type": "integer" + } + }, + "required": [ + "x", + "y", + "z", + "dimension" + ] + }, + "parent": { + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + }, + "z": { + "type": "integer" + } + }, + "required": [ + "x", + "y", + "z" + ] + }, + "tail": { + "type": "object", + "properties": { + "linkType" : { + "type": "number" + }, + "destination":{ + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + }, + "z": { + "type": "integer" + }, + "dimension": { + "type": "integer" + } + }, + "required": [ + "x", + "y", + "z", + "dimension" + ] + } + }, + "required": [ + "linkType" + ] + }, + "lock":{ + "type": "object", + "properties": { + "lockState": { + "type": "boolean" + }, + "lockKey": { + "type": "number" + } + }, + "required": [ + "lockState", + "lockKey" + ] + + } + }, + "required": [ + "children", + "orientation", + "source", + "parent", + "tail" + ] + } + }, + "Orientation": { + "type":"number" + }, + "Origin": { + "type":"object", + "properties":{ + "x": { + "type":"number" + }, + "y": { + "type":"number" + }, + "z": { + "type":"number" + } + }, + "required": [ + "x", + "y", + "z" + ] + }, + "PackDepth": { + "type":"number" + }, + "ParentID": { + "type":"number" + }, + "RootID": { + "type":"number" + }, + "SAVE_DATA_VERSION_ID_INSTANCE": { + "type":"number" + }, + "Tails": { + "type":"array" + } + }, + "required": ["Tails", + "SAVE_DATA_VERSION_ID_INSTANCE", + "RootID", + "ParentID", + "PackDepth", + "Origin", + "Orientation", + "Links", + "IsFilled", + "IsDungeon", + "ID", + "Depth", + "ChildIDs" + ] +}