diff --git a/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java b/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java index 00207782a9..105903ec45 100644 --- a/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java +++ b/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java @@ -40,8 +40,6 @@ public class EntityIO{ this.serializer = serializer; this.name = name; - json.setIgnoreUnknownFields(true); - directory.mkdirs(); //load old revisions diff --git a/core/src/mindustry/core/NetClient.java b/core/src/mindustry/core/NetClient.java index 51a863bcbc..99b7ca9f9d 100644 --- a/core/src/mindustry/core/NetClient.java +++ b/core/src/mindustry/core/NetClient.java @@ -10,7 +10,6 @@ import arc.util.*; import arc.util.CommandHandler.*; import arc.util.io.*; import arc.util.serialization.*; -import arc.util.serialization.JsonValue.*; import mindustry.*; import mindustry.annotations.Annotations.*; import mindustry.audio.*; @@ -434,8 +433,8 @@ public class NetClient implements ApplicationListener{ public static void setRule(String rule, String jsonData){ try{ //readField searches for the specified value, so create a fake parent for it. - tmpJsonMap.child = null; - tmpJsonMap.addChild(rule, new JsonReader().parse(jsonData)); + tmpJsonMap.clear().put(rule, Jval.read(jsonData)); + JsonIO.json.readField(state.rules, rule, tmpJsonMap); }catch(Throwable error){ Log.err("Failed to read rule", error); diff --git a/core/src/mindustry/editor/data/MapPatchesView.java b/core/src/mindustry/editor/data/MapPatchesView.java index 0a2def2ff8..cbd807377c 100644 --- a/core/src/mindustry/editor/data/MapPatchesView.java +++ b/core/src/mindustry/editor/data/MapPatchesView.java @@ -150,8 +150,14 @@ public class MapPatchesView implements AssetView{ int countFields(Jval value){ if(value.isObject() || value.isArray()){ int sum = 0; - for(var child : value){ - sum += countFields(child); + if(value.isObject()){ + for(var child : value.asObject()){ + sum += countFields(child.value); + } + }else{ + for(var child : value.asArray()){ + sum += countFields(child); + } } return Math.max(sum, 1); }else{ diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java index addb32df46..0c135ca6f5 100644 --- a/core/src/mindustry/game/Rules.java +++ b/core/src/mindustry/game/Rules.java @@ -427,8 +427,8 @@ public class Rules{ @Override public void read(Json json, Jval jsonData){ - for(Jval value : jsonData){ - values[Integer.parseInt(value.name)] = json.readValue(TeamRule.class, value); + for(var entry : jsonData.asObject()){ + values[Integer.parseInt(entry.key)] = json.readValue(TeamRule.class, entry.value); } } } diff --git a/core/src/mindustry/io/JsonIO.java b/core/src/mindustry/io/JsonIO.java index ccabb22c0e..76bef04d3c 100644 --- a/core/src/mindustry/io/JsonIO.java +++ b/core/src/mindustry/io/JsonIO.java @@ -5,6 +5,7 @@ import arc.math.geom.*; import arc.util.*; import arc.util.serialization.*; import arc.util.serialization.Json.*; +import arc.util.serialization.Jval.*; import mindustry.*; import mindustry.audio.*; import mindustry.content.*; @@ -25,7 +26,7 @@ public class JsonIO{ public void writeValue(Object value, Class knownType, Class elementType){ if(value instanceof MappableContent c){ try{ - getWriter().value(c.name); + writer.value(c.name); }catch(IOException e){ throw new RuntimeException(e); } @@ -56,7 +57,7 @@ public class JsonIO{ } public static T readBytes(Class type, Class elementType, DataInputStream input) throws IOException{ - return json.readValue(type, elementType, new UBJsonReader().parseWihoutClosing(input)); + return json.readValue(type, elementType, UBJson.read(input)); } public static String write(Object object){ @@ -77,12 +78,12 @@ public class JsonIO{ } public static T read(T base, String string){ - json.readFields(base, new JsonReader().parse(string.replace("io.anuke.", ""))); + json.readFields(base, Jval.read(string.replace("io.anuke.", ""))); return base; } public static String print(String in){ - return json.prettyPrint(in); + return Jval.read(in).toString(Jformat.hjson); } public static void classTag(String tag, Class type){ @@ -312,7 +313,7 @@ public class JsonIO{ public MapObjectives read(Json json, Jval data, Class type){ var exec = new MapObjectives(); // First iteration to instantiate the objectives. - for(var value = data.child; value != null; value = value.next){ + for(var value : data.asArray()){ //glenn why did you implement this in the least backwards compatible way possible //the old objectives had lowercase class tags, now they're uppercase and either way I can't deserialize them without errors if(value.has("class") && Character.isLowerCase(value.getString("class").charAt(0))){ @@ -333,8 +334,8 @@ public class JsonIO{ // Second iteration to map the parents. int i = 0; - for(var value = data.child; value != null; value = value.next, i++){ - for(var parent = value.get("parents").child; parent != null; parent = parent.next){ + for(var entry : data.asArray()){ + for(var parent : entry.get("parents").asArray()){ int val = parent.asInt(); if(val >= 0 && val < exec.all.size){ exec.all.get(i).parents.add(exec.all.get(val)); diff --git a/core/src/mindustry/logic/LogicScript.java b/core/src/mindustry/logic/LogicScript.java index 4f49d76088..ec83ad85cd 100644 --- a/core/src/mindustry/logic/LogicScript.java +++ b/core/src/mindustry/logic/LogicScript.java @@ -67,7 +67,7 @@ public class LogicScript implements JsonSerializable{ public void read(Json json, Jval jsonData){ if(jsonData.isObject()){ timeout = Math.min(maxTimeoutMs, jsonData.getInt("timeout", 0)); - resetVars = jsonData.getBoolean("resetVars", false); + resetVars = jsonData.getBool("resetVars", false); script = jsonData.getString("script", ""); }else{ script = jsonData.asString(); diff --git a/core/src/mindustry/maps/Maps.java b/core/src/mindustry/maps/Maps.java index 93b0e1fe92..be78f4c46a 100644 --- a/core/src/mindustry/maps/Maps.java +++ b/core/src/mindustry/maps/Maps.java @@ -366,7 +366,7 @@ public class Maps{ if(groups == null) return "[]"; StringWriter buffer = new StringWriter(); - JsonIO.json.setWriter(new JsonWriter(buffer)); + JsonIO.json.setWriter(new StringJsonWriter(buffer)); JsonIO.json.writeArrayStart(); for(int i = 0; i < groups.size; i++){ diff --git a/core/src/mindustry/mod/ContentParser.java b/core/src/mindustry/mod/ContentParser.java index 3be455d5e4..424f462229 100644 --- a/core/src/mindustry/mod/ContentParser.java +++ b/core/src/mindustry/mod/ContentParser.java @@ -96,9 +96,9 @@ public class ContentParser{ throw new IllegalArgumentException("Attribute definitions must be objects, e.g. {heat: 10}"); } Attributes attr = new Attributes(); - for(var child : data){ - Attribute value = Attribute.exists(child.name) ? Attribute.get(child.name) : Attribute.add(child.name); - attr.set(value, child.asFloat()); + for(var entry : data.asObject()){ + Attribute value = Attribute.exists(entry.key) ? Attribute.get(entry.key) : Attribute.add(entry.key); + attr.set(value, entry.value.asFloat()); } return attr; }); @@ -127,7 +127,7 @@ public class ContentParser{ } } TextureRegion result = Core.atlas.find(str); - if(!result.found()){ + if(!result.found() && !str.equalsIgnoreCase("error")){ warn("Sprite not found: '" + str + "'"); } return result; @@ -242,21 +242,18 @@ public class ContentParser{ //no singular operation, check for multi-operation if(opval == null){ - Jval opsVal = - data.has("operations") ? data.get("operations") : - data.has("ops") ? data.get("ops") : null; + var opsVal = + data.has("operations") ? data.get("operations").asArray() : + data.has("ops") ? data.get("ops").asArray() : null; if(opsVal != null){ - if(!opsVal.isArray()) throw new RuntimeException("Chained PartProgress operations must be an array."); - int i = 0; - while(true){ + for(int i = 0; i < opsVal.size; i ++){ Jval val = opsVal.get(i); if(val == null) break; Jval op = val.has("operation") ? val.get("operation") : val.has("op") ? val.get("op") : null; base = parseProgressOp(base, op.asString(), val); - i++; } } @@ -284,21 +281,21 @@ public class ContentParser{ } //transform array format - if(data.isArray() && data.size == 3){ + if(data.isArray() && data.asArray().size == 3){ return new Mat3D().setToTranslation(new Vec3(data.asFloatArray())); } Mat3D mat = new Mat3D(); //TODO this is kinda bad - for(var val : data){ - switch(val.name){ + for(var entry : data.asObject()){ + switch(entry.key){ case "translate", "trans" -> mat.translate(parser.readValue(Vec3.class, data)); case "scale", "scl" -> mat.scale(parser.readValue(Vec3.class, data)); case "rotate", "rot" -> mat.rotate(parser.readValue(Vec3.class, data), data.getFloat("degrees", 0f)); case "multiply", "mul" -> mat.mul(parser.readValue(Mat3D.class, data)); case "x", "y", "z" -> {} - default -> throw new RuntimeException("Unknown matrix transformation: '" + val.name + "'"); + default -> throw new RuntimeException("Unknown matrix transformation: '" + entry.key + "'"); } } @@ -454,7 +451,7 @@ public class ContentParser{ //try to parse env bits if((type == int.class || type == Integer.class) && jsonData.isArray()){ int value = 0; - for(var str : jsonData){ + for(var str : jsonData.asArray()){ if(!str.isString()) throw new SerializationException("Integer bitfield values must all be strings. Found: " + str); String field = str.asString(); value |= Reflect.get(Env.class, field); @@ -467,7 +464,7 @@ public class ContentParser{ if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){ String[] split = jsonData.asString().split("/"); - return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}"); + return (T)new ItemStack(fromJson(Item.class, split[0]), Integer.parseInt(split[1])); } //try to parse "payloaditem/amount" syntax @@ -490,8 +487,9 @@ public class ContentParser{ } //try to parse Rect as array - if(type == Rect.class && jsonData.isArray() && jsonData.size == 4){ - return (T)new Rect(jsonData.get(0).asFloat(), jsonData.get(1).asFloat(), jsonData.get(2).asFloat(), jsonData.get(3).asFloat()); + if(type == Rect.class && jsonData.isArray() && jsonData.asArray().size == 4){ + JsonArray arr = jsonData.asArray(); + return (T)new Rect(arr.get(0).asFloat(), arr.get(1).asFloat(), arr.get(2).asFloat(), arr.get(3).asFloat()); } //search across different content types to find one by name @@ -502,7 +500,7 @@ public class ContentParser{ return found; } } - throw new IllegalArgumentException("\"" + jsonData.name + "\": No content found with name '" + jsonData.asString() + "'."); + throw new IllegalArgumentException("No content found with name '" + jsonData.asString() + "'."); } if(Content.class.isAssignableFrom(type)){ @@ -513,7 +511,7 @@ public class ContentParser{ T two = (T)Vars.content.getByName(ctype, jsonData.asString()); if(two != null) return two; - throw new IllegalArgumentException((jsonData.name == null ? "" : "\"" + jsonData.name + "\": ") + "No " + ctype + " found with name '" + jsonData.asString() + "'.\nMake sure '" + jsonData.asString() + "' is spelled correctly, and that it really exists!\nThis may also occur because its file failed to parse."); + throw new IllegalArgumentException("No " + ctype + " found with name '" + jsonData.asString() + "'.\nMake sure '" + jsonData.asString() + "' is spelled correctly, and that it really exists!\nThis may also occur because its file failed to parse."); } } @@ -522,8 +520,11 @@ public class ContentParser{ }; public void readBlockConsumers(Block block, Jval value){ - for(Jval child : value){ - switch(child.name){ + for(var entry : value.asObject()){ + String name = entry.key; + Jval child = entry.value; + + switch(name){ case "remove" -> { String[] values = child.isString() ? new String[]{child.asString()} : child.asStringArray(); for(String type : values){ @@ -569,7 +570,7 @@ public class ContentParser{ } } case "powerBuffered" -> block.consumePowerBuffered(child.asFloat()); - default -> throw new IllegalArgumentException("Unknown consumption type: '" + child.name + "' for block '" + block.name + "'."); + default -> throw new IllegalArgumentException("Unknown consumption type: '" + name + "' for block '" + block.name + "'."); } } value.remove("consumes"); @@ -1024,15 +1025,10 @@ public class ContentParser{ public Content parse(LoadedMod mod, String name, String json, Fi file, ContentType type) throws Exception{ checkInit(); - //remove extra # characters to make it valid json... apparently some people have *unquoted* # characters in their json - if(file.extension().equals("json")){ - json = json.replace("#", "\\#"); - } - currentFile = file; currentMod = mod; - var rawValue = parser.fromJson(null, Jval.read(json).toString(Jformat.plain)); + var rawValue = parser.fromJson(null, json); if(!(rawValue instanceof Jval value)) throw new SerializationException("Content JSON must be an object, not a single value."); if(!parsers.containsKey(type)){ @@ -1113,12 +1109,7 @@ public class ContentParser{ } private GenericMesh[] parseMeshes(Planet planet, Jval array){ - var res = new GenericMesh[array.size]; - for(int i = 0; i < array.size; i++){ - //yes get is O(n) but it's practically irrelevant here - res[i] = parseMesh(planet, array.get(i)); - } - return res; + return array.asArray().map(value -> parseMesh(planet, value)).toArray(GenericMesh.class); } private GenericMesh parseMesh(Planet planet, Jval data){ @@ -1274,15 +1265,18 @@ public class ContentParser{ toBeParsed.remove(object); var type = object.getClass(); var fields = parser.getFields(type); - for(Jval child = jsonMap.child; child != null; child = child.next){ - FieldMetadata metadata = fields.get(child.name().replace(" ", "_")); + for(var entry : jsonMap.asObject()){ + String name = entry.key; + Jval child = entry.value; + + FieldMetadata metadata = fields.get(name.replace(" ", "_")); if(metadata == null){ if(ignoreUnknownFields){ - warn("@Unknown field '@' for class '@'", currentContent == null ? "" : "[" + currentContent.minfo.sourceFile.name() + "]: ", child.name, type.getSimpleName()); + warn("@Unknown field '@' for class '@'", currentContent == null ? "" : "[" + currentContent.minfo.sourceFile.name() + "]: ", name, type.getSimpleName()); continue; }else{ - SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")"); - ex.addTrace(child.trace()); + SerializationException ex = new SerializationException("Field not found: " + name + " (" + type.getName() + ")"); + ex.addTrace(child.toString()); throw ex; } } @@ -1301,7 +1295,7 @@ public class ContentParser{ } }else{ boolean isMap = ObjectMap.class.isAssignableFrom(field.getType()) || ObjectIntMap.class.isAssignableFrom(field.getType()) || ObjectFloatMap.class.isAssignableFrom(field.getType()); - boolean mergeMap = isMap && child.has("add") && child.get("add").isBoolean() && child.getBoolean("add", false); + boolean mergeMap = isMap && child.has("add") && child.get("add").isBoolean() && child.getBool("add", false); if(mergeMap){ child.remove("add"); @@ -1330,7 +1324,7 @@ public class ContentParser{ throw ex; }catch(RuntimeException runtimeEx){ SerializationException ex = new SerializationException(runtimeEx); - ex.addTrace(child.trace()); + ex.addTrace(child.toString()); ex.addTrace(field.getName() + " (" + type.getName() + ")"); throw ex; } @@ -1370,8 +1364,10 @@ public class ContentParser{ currentMod = cur; currentFile = file; + boolean isObject = research.isObject(); + //add custom objectives - if(research.has("objectives")){ + if(isObject && research.has("objectives")){ node.objectives.addAll(parser.readValue(Objective[].class, research.get("objectives"))); } @@ -1389,13 +1385,13 @@ public class ContentParser{ node.setupRequirements(unlock.researchRequirements()); } - if(research.has("planet")){ + if(isObject && research.has("planet")){ node.planet = find(ContentType.planet, research.getString("planet")); } - if(research.getBoolean("root", false)){ + if(isObject && research.getBool("root", false)){ node.name = research.getString("name", unlock.name); - node.requiresUnlock = research.getBoolean("requiresUnlock", false); + node.requiresUnlock = research.getBool("requiresUnlock", false); TechTree.roots.add(node); }else{ if(researchName != null){ diff --git a/core/src/mindustry/mod/DataPatcher.java b/core/src/mindustry/mod/DataPatcher.java index cb91a46680..2e3367bc8d 100644 --- a/core/src/mindustry/mod/DataPatcher.java +++ b/core/src/mindustry/mod/DataPatcher.java @@ -249,8 +249,8 @@ public class DataPatcher{ set.name = value.getString("name", ""); value.remove("name"); //patchsets can have a name, ignore it if present - for(var child : value){ - assign(root, child.name, child, null, null, null); + for(var entry : value.asObject()){ + assign(root, entry.key, entry.value, null, null, null); } currentlyApplyingPatch = null; @@ -413,8 +413,8 @@ public class DataPatcher{ if(object == root){ if(value instanceof Jval jval && jval.isObject()){ - for(var child : jval){ - assign(root, field + "." + child.name, child, null, null, null); + for(var entry : jval.asObject()){ + assign(root, field + "." + entry.key, entry.value, null, null, null); } }else{ warn("Content '@' cannot be assigned.", field); @@ -661,14 +661,14 @@ public class DataPatcher{ //assign each field manually var childFields = parser.getJson().getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass()); - for(var child : jsv){ - if(child.name != null){ - assign(prevValue, child.name, child, + for(var entry : jsv.asObject()){ + if(entry.key != null){ + assign(prevValue, entry.key, entry.value, metadata != null && (metadata.type == ObjectMap.class || metadata.type == ObjectFloatMap.class) ? metadata : metadata != null && metadata.type == Seq.class ? new FieldData(metadata.elementType, null, null) : metadata != null && metadata.type.isArray() ? new FieldData(metadata.type.getComponentType(), null, null) : - !childFields.containsKey(child.name) ? null : - new FieldData(childFields.get(child.name)), object, field); + !childFields.containsKey(entry.key) ? null : + new FieldData(childFields.get(entry.key)), object, field); } } } diff --git a/core/src/mindustry/mod/data/PatchAsset.java b/core/src/mindustry/mod/data/PatchAsset.java index ad7fc01039..ba32d0a4c2 100644 --- a/core/src/mindustry/mod/data/PatchAsset.java +++ b/core/src/mindustry/mod/data/PatchAsset.java @@ -69,6 +69,6 @@ public class PatchAsset extends DataAsset{ @Override public String toString(){ //the json can be a single 'error' value if it failed to parse - return !json.isObject() ? patch : json.prettyPrint(OutputType.minimal, 2); + return !json.isObject() ? patch : json.toString(Jformat.hjson); } } diff --git a/core/src/mindustry/type/MapLocales.java b/core/src/mindustry/type/MapLocales.java index d1a55ebb0b..11c8247b82 100644 --- a/core/src/mindustry/type/MapLocales.java +++ b/core/src/mindustry/type/MapLocales.java @@ -20,14 +20,14 @@ public class MapLocales extends ObjectMap implements JsonSeri @Override public void read(Json json, Jval jsonData){ - for(Jval value : jsonData){ + for(var entry : jsonData.asObject()){ StringMap map = new StringMap(); - for(Jval child = value.child; child != null; child = child.next){ - map.put(child.name, json.readValue(String.class, null, child)); + for(var innerEntry : entry.value.asObject()){ + map.put(innerEntry.key, innerEntry.value.asString()); } - put(value.name, map); + put(entry.key, map); } } diff --git a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java index 5185927536..f7a60d8060 100644 --- a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java @@ -256,7 +256,7 @@ public class CustomRulesDialog extends BaseDialog{ Boolp allowMusic = () -> !rules.disableMusic; Func> parser = str -> { try{ - return Seq.map(new JsonReader().parse("[" + str + "]").asStringArray(), MusicContainer::new); + return Jval.read("[" + str + "]").asArray().map( j -> new MusicContainer(j.asString())); }catch(Throwable e){ return null; } diff --git a/gradle.properties b/gradle.properties index 490f71eece..894accf47f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=c2632c8f1b +archash=36256993a0 diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index 83ff28c363..b18a382e89 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -6,6 +6,7 @@ import arc.struct.*; import arc.util.*; import arc.util.io.*; import arc.util.serialization.*; +import arc.util.serialization.Jval.*; import mindustry.*; import mindustry.content.*; import mindustry.core.*; @@ -231,7 +232,7 @@ public class ApplicationTests{ for(String file : files){ try{ String str = Core.files.absolute("./../../" + file).readString(); - assertEquals(ValueType.array, new JsonReader().parse(str).type()); + assertEquals(Jtype.array, Jval.read(str).getType()); assertTrue(Jval.read(str).isArray()); JSONArray array = new JSONArray(str); assertTrue(array.length() > 0);