|
1 | | -# JSONConfigProcessors |
| 1 | +# JSONConfigProcessors |
| 2 | + |
| 3 | +Is an abstract class from which any custom process must extend. The functionality of this class is a bit advanced, but basically, it serves as a way to create functions to process the user input before or after the property tests. |
| 4 | + |
| 5 | +## Usage |
| 6 | + |
| 7 | +This plugin uses processors as a way to allow the user to execute any custom code during the JSON validation process, in case the functionality provided by this framework is not enough. This page would explain how to use them. But first, let's discuss some behaviour of the validation process. |
| 8 | + |
| 9 | +Each property execute the validation process in three phases: |
| 10 | +1. **Preprocess:** The user determines this phase behaviour. However, it can not modify the input data. |
| 11 | +2. **Type validation:** This phase executes all the validation tests that we have been discussing in other parts of this documentation. |
| 12 | +3. **Postprocess:** The user determines this phase behaviour. This phase can modify the input data. |
| 13 | + |
| 14 | +Whenever an error arises in any of these phases, the process would not execute the following ones. On the other hand, raising warnings would not stop the validation process. |
| 15 | + |
| 16 | +Also, when the process validates a dictionary or [JSONPropertyObject](./JSON-PROPERTY-OBJECT.md), it first executes the preprocess, then its type validation consists on the preprocess, type validation, and postprocess cycle of each of the dictionary properties, and finally it executes the postprocess. The same occurs in [JSONPropertyArray](./JSON-PROPERTY-ARRAY.md). |
| 17 | + |
| 18 | +Finally, on a side note, the process validates the properties of a dictionary in the order they were defined. This fact becomes significant when the custom processes writte and read global variables. |
| 19 | + |
| 20 | +Now we would explain what you can do when using custom processes: |
| 21 | +- You can access the property to call its methods, using the 'get_property' method. |
| 22 | +- You can raise custom errors and warnings, using the 'add_error' and 'add_warning' metho. |
| 23 | +- You can modify the output of a certain property, but only while postprocessing. |
| 24 | +- You can set global variables that can be accessed or modify at any point of the validation process, using the 'set_variable', 'has_variable' and 'get_variable' methods. |
| 25 | + |
| 26 | +When creating a new processor, we would need to overwrite the '_preprocess' method, which does not receive the input data and does not return anything, or the '_postprocess' method, which gets the input data as a parameter and returns an output. |
| 27 | + |
| 28 | +## Example: Properties determine the validation process of another property |
| 29 | + |
| 30 | +In this example, the JSON file would contain a 'min' and a 'max' property that would set the minimum and the maximum value of another property 'value': |
| 31 | + |
| 32 | +```JSON |
| 33 | +{ |
| 34 | + "min": 0, |
| 35 | + "max": 5, |
| 36 | + "value": 2 |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +To achieve this, we would need three processors in three different scripts. First, the 'set_min' postprocessor: |
| 41 | + |
| 42 | +```GDScript |
| 43 | +extends JSONConfigProcessor |
| 44 | +
|
| 45 | +# Overwrite the '_postprocess' method |
| 46 | +func _postprocess(minimum : int): |
| 47 | + # Set a global variable called 'min' |
| 48 | + set_variable("min", minimum) |
| 49 | +
|
| 50 | + # It is a postprocessor, so it must return a value |
| 51 | + return minimum |
| 52 | +``` |
| 53 | + |
| 54 | +The 'set_max' postprocessor: |
| 55 | + |
| 56 | +```GDScript |
| 57 | +extends JSONConfigProcessor |
| 58 | +
|
| 59 | +# Overwrite the '_postprocess' method |
| 60 | +func _postprocess(maximum : int): |
| 61 | + # Set a global variable called 'max' |
| 62 | + set_variable("max", maximum) |
| 63 | +
|
| 64 | + # It is a postprocessor, so it must return a value |
| 65 | + return maximum |
| 66 | +``` |
| 67 | + |
| 68 | +The 'set_range' preprocessor. It is crucial to notice that we need to check if the variables exists. Just in case the input data is non-valid, and the variables are not declared: |
| 69 | + |
| 70 | +```GDScript |
| 71 | +extends JSONConfigProcessor |
| 72 | +
|
| 73 | +# Overwrite the '_preprocess' method |
| 74 | +func _preprocess(): |
| 75 | + # Check if the global variable min exists |
| 76 | + if has_variable("min"): |
| 77 | + # Set the integer minimum value |
| 78 | + get_property().set_min_value(get_variable("min")) |
| 79 | + # Check if the global variable max exists |
| 80 | + if has_variable("max"): |
| 81 | + # Set the integer maximum value |
| 82 | + get_property().set_max_value(get_variable("max")) |
| 83 | +
|
| 84 | + # It is a preprocessor, so it does not return a value |
| 85 | +``` |
| 86 | + |
| 87 | +Now we can declare a new JSON configuration file that would check if 'value' is between 'min' and 'max': |
| 88 | + |
| 89 | +```GDScript |
| 90 | +# Create the 'min' property |
| 91 | +var min_value = JSONPropertyInteger.new() |
| 92 | +min_value.set_postprocessor(preload("res://set_min.gd").new()) |
| 93 | +
|
| 94 | +# Create the 'max' property |
| 95 | +var max_value = JSONPropertyInteger.new() |
| 96 | +max_value.set_postprocessor(preload("res://set_max.gd").new()) |
| 97 | +
|
| 98 | +# Create the 'value' property |
| 99 | +var value = JSONPropertyInteger.new() |
| 100 | +value.set_preprocessor(preload("res://set_range.gd").new()) |
| 101 | +
|
| 102 | +# Create the JSON configuration file |
| 103 | +var json_config_file = JSONConfigFile.new() |
| 104 | +# Add the 'min' property |
| 105 | +json_config_file.add_property("min", min_value) |
| 106 | +# Add the 'max' property |
| 107 | +json_config_file.add_property("max", max_value) |
| 108 | +# Add the 'value' property |
| 109 | +json_config_file.add_property("value", value) |
| 110 | +
|
| 111 | +# Validate input |
| 112 | +json_config_file.validate(json_file_path) |
| 113 | +``` |
| 114 | + |
| 115 | +## Example: Transforming an enum into an integer |
| 116 | +In this case, we want the user to introduce an enum. This field would be a string on the final dictionary, but we can transform this field to be an integer that represents a value of a Godot's enum. For this purpose, we must define a postprocessor called 'transform_gender_enum' that looks like this: |
| 117 | + |
| 118 | +```GDScript |
| 119 | +extends JSONConfigProcessor |
| 120 | +
|
| 121 | +
|
| 122 | +enum Genders{ |
| 123 | + MALE, |
| 124 | + FEMALE, |
| 125 | + NON_BINARY |
| 126 | +} |
| 127 | +
|
| 128 | +
|
| 129 | +func _postprocess(gender : String): |
| 130 | + match gender: |
| 131 | + "MALE": |
| 132 | + return Genders.MALE |
| 133 | + "FEMALE": |
| 134 | + return Genders.FEMALE |
| 135 | + "NON_BINARY": |
| 136 | + return Genders.NON_BINARY |
| 137 | +
|
| 138 | + return -1 |
| 139 | +``` |
| 140 | + |
| 141 | +Then we can create the following JSON configuration file: |
| 142 | + |
| 143 | +```GDScript |
| 144 | +# Create the 'gender' property |
| 145 | +var gender = JSONPropertyEnum.new() |
| 146 | +gender.set_enum(["MALE", "FEMALE", "NON_BINARY"]) |
| 147 | +gender.set_postprocessor(preload("res://transform_gender_enum.gd").new()) |
| 148 | +
|
| 149 | +# Create the JSON configuration file |
| 150 | +var json_config_file = JSONConfigFile.new() |
| 151 | +# Add the 'gender' property |
| 152 | +json_config_file.add_property("gender", gender) |
| 153 | +
|
| 154 | +# Validate input |
| 155 | +json_config_file.validate(json_file_path) |
| 156 | +``` |
| 157 | + |
| 158 | +Using this method, this JSON file: |
| 159 | + |
| 160 | +```JSON |
| 161 | +{ |
| 162 | + "gender": "MALE" |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +Would generate this dictionary: |
| 167 | + |
| 168 | +```GDScript |
| 169 | +{ |
| 170 | + "gender": Genders.MALE |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +## Example: Primes validation |
| 175 | + |
| 176 | +In this last example, we would raise a custom error. We will check if an integer is a prime. For this purpose, we need to create another postprocessor called 'prime_check': |
| 177 | + |
| 178 | +```GDScript |
| 179 | +extends JSONConfigProcessor |
| 180 | +
|
| 181 | +
|
| 182 | +func _postprocess(integer : int): |
| 183 | + # One is not a prime >:( |
| 184 | + if integer == 1: |
| 185 | + add_error({"error": "This is not a prime"}) |
| 186 | + return null |
| 187 | +
|
| 188 | + # A simple prime check |
| 189 | + for i in range(2, sqrt(integer) + 1): |
| 190 | + # If is not prime |
| 191 | + if integer % i == 0: |
| 192 | + add_error({"error": "This is not a prime"}) |
| 193 | + return null |
| 194 | +
|
| 195 | + return integer |
| 196 | +``` |
| 197 | + |
| 198 | +Now we can create the following JSON configuration file: |
| 199 | + |
| 200 | +```GDScript |
| 201 | +# Create the 'prime' property |
| 202 | +var prime = JSONPropertyInteger.new() |
| 203 | +prime.set_postprocessor(preload("res://prime_check.gd").new()) |
| 204 | +
|
| 205 | +# Create the JSON configuration file |
| 206 | +var json_config_file = JSONConfigFile.new() |
| 207 | +# Add the 'prime' property |
| 208 | +json_config_file.add_property("prime", prime) |
| 209 | + |
| 210 | +# Validate input |
| 211 | +json_config_file.validate(json_file_path) |
| 212 | +``` |
| 213 | + |
| 214 | +In this case, this JSON file would not raise any error: |
| 215 | + |
| 216 | +```JSON |
| 217 | +{ |
| 218 | + "prime": 7 |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +This file, on the other hand, would raise our custom error: |
| 223 | + |
| 224 | +```JSON |
| 225 | +{ |
| 226 | + "prime": 4 |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +Returned error: |
| 231 | + |
| 232 | +```GDScript |
| 233 | +[ |
| 234 | + { |
| 235 | + "error": "This is not a prime", |
| 236 | + "context": "prime", |
| 237 | + "as_text": "This is not a prime, at 'prime'." |
| 238 | + } |
| 239 | +] |
| 240 | +``` |
0 commit comments