From 7fda6fdfe5a44bdd97e70e3e1ca9c8d439f83892 Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Sat, 2 Aug 2025 16:42:40 +0200 Subject: [PATCH] feat(NightJson): support minified JSON files --- .../midnightdust/yaytris/util/NightJson.java | 161 ++++++++++-------- 1 file changed, 87 insertions(+), 74 deletions(-) diff --git a/src/main/java/eu/midnightdust/yaytris/util/NightJson.java b/src/main/java/eu/midnightdust/yaytris/util/NightJson.java index 7af59bb..80980e6 100644 --- a/src/main/java/eu/midnightdust/yaytris/util/NightJson.java +++ b/src/main/java/eu/midnightdust/yaytris/util/NightJson.java @@ -5,8 +5,11 @@ import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; +import java.nio.file.Files; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /* NightJson v0.2 by Martin Prokoph @@ -14,7 +17,7 @@ import java.util.concurrent.atomic.AtomicReference; Concept inspired by GSON */ public class NightJson { - private static final String KEY_PATTERN = "\"(-?.*)\":"; + private static final String KEY_PATTERN = "\"(-?[A-Za-z-_.]*)\":"; Class jsonClass; Field jsonMap; String fileLocation; @@ -28,6 +31,7 @@ public class NightJson { } catch (NoSuchFieldException ignored) {} } + @SuppressWarnings("unchecked") public void writeJson() { if (fileLocation == null) return; try { @@ -39,38 +43,37 @@ public class NightJson { while (it.hasNext()) { Field field = it.next(); if (field == jsonMap) continue; - writeElement(jsonFile, field.get(null), field.getType(), field.getName()); - jsonFile.write(it.hasNext() ? ",\n" : "\n"); + writeElement(jsonFile, field.get(null), field.getType(), field.getName(), it.hasNext()); } } if (jsonMap != null) { - //noinspection unchecked Iterator it = ((Map)jsonMap.get(null)).keySet().iterator(); while (it.hasNext()) { String key = it.next(); Object value = jsonMap.get(key); - writeElement(jsonFile, jsonMap.get(key), value.getClass(), key); - jsonFile.write(it.hasNext() ? ",\n" : "\n"); + writeElement(jsonFile, jsonMap.get(key), value.getClass(), key, it.hasNext()); } } jsonFile.write("}"); jsonFile.close(); } catch (IOException | IllegalAccessException e) { System.out.println("Oh no! An Error occurred whilst writing the JSON file :("); - e.fillInStackTrace(); + e.printStackTrace(); } } - private void writeElement(FileWriter jsonFile, Object value, Class type, String name) throws IOException, IllegalAccessException { + private void writeElement(FileWriter jsonFile, Object value, Class type, String name, boolean hasNext) throws IOException, IllegalAccessException { jsonFile.write("\t"); if (type == Comment.class) { - jsonFile.write(String.format("// %s\n", ((Comment) value).commentString)); + jsonFile.write(String.format("\n\t// %s\n", ((Comment) value).commentString)); return; } jsonFile.write(String.format("\"%s\": ", name)); jsonFile.write(objToString(value, type)); + jsonFile.write(hasNext ? ",\n" : "\n"); } + @SuppressWarnings("unchecked") public void readJson() { if (fileLocation == null) return; try { @@ -80,49 +83,69 @@ public class NightJson { return; } - Scanner jsonFile = new Scanner(file); - Map jsonKeyValuePairs = new HashMap<>(); - AtomicReference lastKey = new AtomicReference<>(); - jsonFile.forEachRemaining(s -> { - if (!s.matches("[{}]") && !s.matches("//+")) { - if (s.matches(KEY_PATTERN)) { - lastKey.set(s.replaceAll("([\":])", "")); - jsonKeyValuePairs.put(lastKey.get(), ""); - } - else { - String val = s.replaceAll("(\")", ""); - if (val.endsWith(",")) val = val.substring(0, val.length()-1); - jsonKeyValuePairs.put(lastKey.get(), (jsonKeyValuePairs.get( - lastKey.get()).isEmpty() ? "" : jsonKeyValuePairs.get(lastKey.get()) + " " - ) + val); - } - } - }); + Map asMap = jsonToMap(Files.readString(file.toPath()).replaceAll("(//)+.*\n", ""), (key) -> getField(key).isPresent() ? getField(key).get().getType() : String.class); - for (String key : jsonKeyValuePairs.keySet()) { - String currentString = jsonKeyValuePairs.get(key); - //System.out.printf("Key: %s Value: %s%n", key, currentString); - boolean isInClass = false; - if (jsonClass != null) { - Field field; - try { - field = jsonClass.getField(key); - field.set(field, stringToFieldObj(currentString, field)); - isInClass = true; - } catch (NoSuchFieldException ignored) {} + for (String key : asMap.keySet()) { + Object value = asMap.get(key); + Optional field = getField(key); + if (field.isPresent()) { + field.get().set(null, value); } - if (jsonMap != null && !isInClass) { - //noinspection unchecked - ((Map)jsonMap.get(null)).put(key, stringToObj(currentString, getTypeArgument(jsonMap, 1))); + else if (jsonMap != null) { + ((Map)jsonMap.get(null)).put(key, value); } } - jsonFile.close(); - } catch (IOException | IllegalAccessException | NoSuchElementException e) { + } catch (IOException | IllegalAccessException | NoSuchElementException | ClassCastException e) { System.out.println("Oh no! An Error occurred whilst reading the JSON file :("); - e.fillInStackTrace(); + e.printStackTrace(); } } + private Map jsonToMap(String jsonString, Function> keyToType) { + Map map = new HashMap<>(); + Iterator pairIterator = Arrays.stream(jsonString.replaceAll("(//)+.*\n", "").replaceFirst("[{]", "").split(",")).iterator(); + while (pairIterator.hasNext()) { + String s = pairIterator.next(); + + Matcher matcher = Pattern.compile(KEY_PATTERN).matcher(s); + if (matcher.find()) { + String key = matcher.group().replaceAll("([\":])", ""); + String val = s.split(KEY_PATTERN, 2)[1]; + + StringBuilder submapString = new StringBuilder(); + if (s.contains("{")) { + int level = charAmount(s, '{'); + submapString.append(val); + if (pairIterator.hasNext()) submapString.append(","); + while (pairIterator.hasNext()) { + String next = pairIterator.next(); + submapString.append(next); + if (next.contains("{")) level += charAmount(next, '{'); + if (next.contains("}")) level -= charAmount(next, '}'); + if (level <= 0) break; + if (pairIterator.hasNext()) submapString.append(","); + } + System.out.println(submapString); + } + if (submapString.length() > 0) { + Optional field = getField(key); + map.put(key, jsonToMap(String.valueOf(submapString), k -> field.isPresent() ? getTypeArgument(field.get(), 1) : String.class)); + } + else { + if (val.startsWith(" ")) val = val.substring(1); + val = val.replaceAll("[\"}\n]", ""); + if (val.endsWith(",")) val = val.substring(0, val.length() - 1); + + map.put(key, stringToObj(val, keyToType.apply(key))); + } + } + } + return map; + } + private int charAmount(String input, char c) { + return (int) input.chars().filter(ch -> ch == c).count(); + } + private String objToString(Object value, Class type) { if (type == Map.class) { StringBuilder mapPairs = new StringBuilder(); @@ -141,36 +164,18 @@ public class NightJson { return String.format(type == String.class || type.isEnum() ? "\"%s\"" : "%s", value); } - private Object stringToFieldObj(String currentString, Field field) { - if (field.getType() == Map.class) { - Map map = new HashMap<>(); - Iterator it = Arrays.stream(currentString.substring(1, currentString.length()-1).split(",")).iterator(); - while (it.hasNext()) { - String pair = it.next(); - if (!pair.contains(":")) break; - int semicolonPos = pair.indexOf(":"); - Class keyType = getTypeArgument(field, 0); - Class valType = getTypeArgument(field, 1); - map.put(stringToObj(pair.substring(0, semicolonPos), keyType), - stringToObj(pair.substring(semicolonPos+2), valType)); - } - return map; - } - return stringToObj(currentString, field.getType()); - } - - private Object stringToObj(String currentString, Class type) { + private Object stringToObj(String value, Class type) { switch (type.getName()) { - case "byte": return Byte.parseByte(currentString); - case "int": return Integer.parseInt(currentString); - case "long": return Long.parseLong(currentString); - case "float": return Float.parseFloat(currentString); - case "double": return Double.parseDouble(currentString); - case "boolean": return Boolean.parseBoolean(currentString); + case "byte": return Byte.parseByte(value); + case "int": return Integer.parseInt(value); + case "long": return Long.parseLong(value); + case "float": return Float.parseFloat(value); + case "double": return Double.parseDouble(value); + case "boolean": return Boolean.parseBoolean(value); } if (type.isEnum()) return Arrays.stream(type.getEnumConstants()) - .filter(enumConstant -> Objects.equals(enumConstant.toString(), currentString)).findFirst().orElseThrow(); - else return currentString; + .filter(enumConstant -> Objects.equals(enumConstant.toString(), value)).findFirst().orElseThrow(); + else return value; } private static Class getTypeArgument(Field field, int index) { @@ -182,10 +187,18 @@ public class NightJson { } catch (NoSuchFieldException | IllegalAccessException ignored) { return rawType; } } + private Optional getField(String name) { + try { + return Optional.of(jsonClass.getField(name)); + } catch (NoSuchFieldException e) { + return Optional.empty(); + } + } + public static class Comment { final String commentString; - public Comment(String commentString) { - this.commentString = commentString; + public Comment(String commentString, Object... args) { + this.commentString = String.format(commentString, args); } } }