");
+ System.err.println("Example: zon encode data.json > data.zonf");
+ System.err.println(" zon decode data.zonf > output.json");
+ }
+}
diff --git a/src/main/java/com/zonformat/zon/Constants.java b/src/main/java/com/zonformat/zon/Constants.java
new file mode 100644
index 0000000..ab62a73
--- /dev/null
+++ b/src/main/java/com/zonformat/zon/Constants.java
@@ -0,0 +1,39 @@
+/*
+ * ZON Protocol Constants v1.0.5
+ *
+ * Copyright (c) 2025 ZON-FORMAT (Roni Bhakta)
+ * MIT License
+ */
+package com.zonformat.zon;
+
+/**
+ * Constants for the ZON format protocol.
+ */
+public final class Constants {
+
+ private Constants() {
+ // Prevent instantiation
+ }
+
+ // Format markers
+ public static final char TABLE_MARKER = '@';
+ public static final char META_SEPARATOR = ':';
+
+ // Reserved tokens (for future use)
+ public static final String GAS_TOKEN = "_";
+ public static final String LIQUID_TOKEN = "^";
+
+ // Default anchor interval for large datasets
+ public static final int DEFAULT_ANCHOR_INTERVAL = 100;
+
+ // Security limits (DOS prevention)
+ public static final long MAX_DOCUMENT_SIZE = 100L * 1024 * 1024; // 100 MB
+ public static final int MAX_LINE_LENGTH = 1024 * 1024; // 1 MB
+ public static final int MAX_ARRAY_LENGTH = 1_000_000; // 1 million items
+ public static final int MAX_OBJECT_KEYS = 100_000; // 100K keys
+ public static final int MAX_NESTING_DEPTH = 100; // Maximum nesting depth
+
+ // Legacy compatibility
+ public static final char LEGACY_TABLE_MARKER = '@';
+ public static final int INLINE_THRESHOLD_ROWS = 0;
+}
diff --git a/src/main/java/com/zonformat/zon/Zon.java b/src/main/java/com/zonformat/zon/Zon.java
new file mode 100644
index 0000000..3d9ae15
--- /dev/null
+++ b/src/main/java/com/zonformat/zon/Zon.java
@@ -0,0 +1,116 @@
+/*
+ * ZON Format v1.0.5
+ * Zero Overhead Notation - A human-readable data serialization format
+ * optimized for LLM token efficiency
+ *
+ * Copyright (c) 2025 ZON-FORMAT (Roni Bhakta)
+ * MIT License
+ */
+package com.zonformat.zon;
+
+/**
+ * Main entry point for ZON encoding and decoding operations.
+ *
+ * ZON (Zero Overhead Notation) is a compact, human-readable data format
+ * optimized for LLM token efficiency. It achieves 35-50% token reduction
+ * vs JSON through tabular encoding, single-character primitives, and
+ * intelligent compression while maintaining 100% data fidelity.
+ *
+ * Quick Start
+ * {@code
+ * // Encode Java data to ZON
+ * Map data = new HashMap<>();
+ * data.put("name", "Alice");
+ * data.put("age", 30);
+ * String zon = Zon.encode(data);
+ *
+ * // Decode ZON back to Java
+ * Object decoded = Zon.decode(zon);
+ * }
+ *
+ * Table Format
+ * Arrays of uniform objects are encoded as tables:
+ * {@code
+ * // Input: [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]
+ * // Output:
+ * // @(2):id,name
+ * // 1,Alice
+ * // 2,Bob
+ * }
+ *
+ * Boolean and Null Encoding
+ *
+ * - {@code true} → {@code T}
+ * - {@code false} → {@code F}
+ * - {@code null} → {@code null}
+ *
+ *
+ * @see ZonEncoder
+ * @see ZonDecoder
+ * @see GitHub Repository
+ */
+public final class Zon {
+
+ private Zon() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Encodes Java data to ZON format.
+ *
+ * Supported types:
+ *
+ * - Objects ({@code Map})
+ * - Arrays ({@code List>})
+ * - Strings
+ * - Numbers (Integer, Long, Double, Float)
+ * - Booleans
+ * - Null
+ *
+ *
+ * @param data Data to encode
+ * @return ZON-formatted string
+ * @throws IllegalArgumentException if circular reference detected
+ */
+ public static String encode(Object data) {
+ return new ZonEncoder().encode(data);
+ }
+
+ /**
+ * Decodes ZON format string to Java objects.
+ *
+ * Uses strict mode by default, which validates:
+ *
+ * - Row counts match declared values
+ * - Field counts match column counts
+ *
+ *
+ * @param zonStr ZON format string
+ * @return Decoded Java object (Map, List, or primitive)
+ * @throws ZonDecodeError if decoding fails or validation errors occur
+ */
+ public static Object decode(String zonStr) {
+ return new ZonDecoder().decode(zonStr);
+ }
+
+ /**
+ * Decodes ZON format string to Java objects with specified strictness.
+ *
+ * @param zonStr ZON format string
+ * @param strict Whether to enable strict validation
+ * @return Decoded Java object (Map, List, or primitive)
+ * @throws ZonDecodeError if decoding fails
+ */
+ public static Object decode(String zonStr, boolean strict) {
+ return new ZonDecoder(strict).decode(zonStr);
+ }
+
+ /**
+ * Gets the library version.
+ *
+ * @return Version string
+ */
+ public static String getVersion() {
+ return "1.0.5";
+ }
+}
diff --git a/src/main/java/com/zonformat/zon/ZonDecodeError.java b/src/main/java/com/zonformat/zon/ZonDecodeError.java
new file mode 100644
index 0000000..52ffe94
--- /dev/null
+++ b/src/main/java/com/zonformat/zon/ZonDecodeError.java
@@ -0,0 +1,106 @@
+/*
+ * ZON Exceptions
+ *
+ * Copyright (c) 2025 ZON-FORMAT (Roni Bhakta)
+ * MIT License
+ */
+package com.zonformat.zon;
+
+/**
+ * Exception thrown when ZON decoding fails.
+ */
+public class ZonDecodeError extends RuntimeException {
+
+ private final String code;
+ private final Integer line;
+ private final Integer column;
+ private final String context;
+
+ /**
+ * Creates a new ZonDecodeError with a message.
+ *
+ * @param message Error message
+ */
+ public ZonDecodeError(String message) {
+ this(message, null, null, null, null);
+ }
+
+ /**
+ * Creates a new ZonDecodeError with a message and error code.
+ *
+ * @param message Error message
+ * @param code Error code (e.g., "E001", "E002")
+ */
+ public ZonDecodeError(String message, String code) {
+ this(message, code, null, null, null);
+ }
+
+ /**
+ * Creates a new ZonDecodeError with full details.
+ *
+ * @param message Error message
+ * @param code Error code
+ * @param line Line number where error occurred
+ * @param column Column position
+ * @param context Relevant context snippet
+ */
+ public ZonDecodeError(String message, String code, Integer line, Integer column, String context) {
+ super(message);
+ this.code = code;
+ this.line = line;
+ this.column = column;
+ this.context = context;
+ }
+
+ /**
+ * Gets the error code.
+ *
+ * @return Error code or null
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * Gets the line number where the error occurred.
+ *
+ * @return Line number or null
+ */
+ public Integer getLine() {
+ return line;
+ }
+
+ /**
+ * Gets the column position.
+ *
+ * @return Column position or null
+ */
+ public Integer getColumn() {
+ return column;
+ }
+
+ /**
+ * Gets the context snippet.
+ *
+ * @return Context or null
+ */
+ public String getContext() {
+ return context;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ZonDecodeError");
+ if (code != null) {
+ sb.append(" [").append(code).append("]");
+ }
+ sb.append(": ").append(getMessage());
+ if (line != null) {
+ sb.append(" (line ").append(line).append(")");
+ }
+ if (context != null) {
+ sb.append("\n Context: ").append(context);
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/zonformat/zon/ZonDecoder.java b/src/main/java/com/zonformat/zon/ZonDecoder.java
new file mode 100644
index 0000000..493eacc
--- /dev/null
+++ b/src/main/java/com/zonformat/zon/ZonDecoder.java
@@ -0,0 +1,806 @@
+/*
+ * ZON Decoder v1.0.5 - Compact Hybrid Format
+ *
+ * Copyright (c) 2025 ZON-FORMAT (Roni Bhakta)
+ * MIT License
+ */
+package com.zonformat.zon;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Decoder for converting ZON format strings back to Java objects.
+ *
+ * Supports both strict and non-strict modes for validation:
+ *
+ * - Strict mode (default): Validates row and field counts
+ * - Non-strict mode: Allows mismatches for lenient parsing
+ *
+ *
+ * Example usage:
+ * {@code
+ * String zon = "users:@(2):id,name\n1,Alice\n2,Bob";
+ * Object decoded = ZonDecoder.decode(zon);
+ * // Returns: {users: [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]}
+ * }
+ */
+public class ZonDecoder {
+
+ private static final Pattern V2_NAMED_PATTERN = Pattern.compile("^@(\\w+)\\((\\d+)\\)(\\[\\w+\\])*:(.+)$");
+ private static final Pattern V2_VALUE_PATTERN = Pattern.compile("^@\\((\\d+)\\)(\\[\\w+\\])*:(.+)$");
+ private static final Pattern V2_PATTERN = Pattern.compile("^@(\\d+)(\\[\\w+\\])*:(.+)$");
+ private static final Pattern V1_PATTERN = Pattern.compile("^@(\\w+)\\((\\d+)\\):(.+)$");
+ private static final Pattern OMITTED_COL_PATTERN = Pattern.compile("\\[(\\w+)\\]");
+ private static final Pattern URL_PATTERN = Pattern.compile("^https?://");
+ private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}");
+ private static final Pattern TIME_PATTERN = Pattern.compile("^\\d{2}:\\d{2}:\\d{2}");
+ private static final Pattern KEY_PATTERN = Pattern.compile("^[a-zA-Z_]\\w*$");
+ private static final Pattern BLOCK_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+\\s*[\\{\\[]");
+
+ private final boolean strict;
+ private int currentLine;
+
+ /**
+ * Creates a new ZonDecoder with strict mode enabled.
+ */
+ public ZonDecoder() {
+ this(true);
+ }
+
+ /**
+ * Creates a new ZonDecoder with specified strictness.
+ *
+ * @param strict Whether to enable strict validation
+ */
+ public ZonDecoder(boolean strict) {
+ this.strict = strict;
+ this.currentLine = 0;
+ }
+
+ /**
+ * Decodes a ZON format string to Java objects.
+ *
+ * @param zonStr ZON format string
+ * @return Decoded Java object (Map, List, or primitive)
+ * @throws ZonDecodeError if decoding fails
+ */
+ public Object decode(String zonStr) {
+ if (zonStr == null || zonStr.isEmpty()) {
+ return new LinkedHashMap<>();
+ }
+
+ // Security: Check document size
+ if (zonStr.length() > Constants.MAX_DOCUMENT_SIZE) {
+ throw new ZonDecodeError(
+ "[E301] Document size exceeds maximum (" + Constants.MAX_DOCUMENT_SIZE + " bytes)",
+ "E301"
+ );
+ }
+
+ String[] lines = zonStr.trim().split("\n");
+ if (lines.length == 0) {
+ return new LinkedHashMap<>();
+ }
+
+ // Special case: Root-level ZON list
+ if (lines.length == 1) {
+ String line = lines[0].trim();
+ if (line.startsWith("[")) {
+ return parseZonNode(line, 0);
+ }
+
+ // Check for colon-less object/array pattern
+ boolean hasBlock = BLOCK_PATTERN.matcher(line).find();
+
+ if (!line.contains(String.valueOf(Constants.META_SEPARATOR)) &&
+ !line.startsWith(String.valueOf(Constants.TABLE_MARKER)) &&
+ !hasBlock) {
+ return parsePrimitive(line);
+ }
+ }
+
+ // Main decode loop
+ Map metadata = new LinkedHashMap<>();
+ Map tables = new LinkedHashMap<>();
+ TableInfo currentTable = null;
+ String currentTableName = null;
+
+ for (String line : lines) {
+ String trimmedLine = line.stripTrailing();
+ currentLine++;
+
+ // Security: Check line length
+ if (trimmedLine.length() > Constants.MAX_LINE_LENGTH) {
+ throw new ZonDecodeError(
+ "[E302] Line length exceeds maximum (" + Constants.MAX_LINE_LENGTH + " chars)",
+ "E302", currentLine, null, null
+ );
+ }
+
+ // Skip blank lines
+ if (trimmedLine.isEmpty()) {
+ continue;
+ }
+
+ // Table header (Anonymous or Legacy): @...
+ if (trimmedLine.startsWith(String.valueOf(Constants.TABLE_MARKER))) {
+ Object[] parsed = parseTableHeader(trimmedLine);
+ currentTableName = (String) parsed[0];
+ currentTable = (TableInfo) parsed[1];
+ tables.put(currentTableName, currentTable);
+ }
+ // Table row (if in a table and haven't read all rows)
+ else if (currentTable != null && currentTable.rowIndex < currentTable.expectedRows) {
+ Map row = parseTableRow(trimmedLine, currentTable);
+ currentTable.rows.add(row);
+
+ // If we've read all rows, exit table mode
+ if (currentTable.rowIndex >= currentTable.expectedRows) {
+ currentTable = null;
+ }
+ }
+ // Metadata line OR Named Table
+ else {
+ int splitIdx = -1;
+ char splitChar = 0;
+ int depth = 0;
+ boolean inQuote = false;
+
+ for (int i = 0; i < trimmedLine.length(); i++) {
+ char c = trimmedLine.charAt(i);
+ if (c == '"') inQuote = !inQuote;
+ if (!inQuote) {
+ if (c == '{' || c == '[') depth++;
+ if (c == '}' || c == ']') depth--;
+
+ if (depth == 1 && (c == '{' || c == '[')) {
+ if (splitIdx == -1) {
+ splitIdx = i;
+ splitChar = c;
+ break;
+ }
+ }
+ if (c == ':' && depth == 0) {
+ splitIdx = i;
+ splitChar = ':';
+ break;
+ }
+ }
+ }
+
+ if (splitIdx != -1) {
+ String key;
+ String val;
+
+ if (splitChar == ':') {
+ key = trimmedLine.substring(0, splitIdx).trim();
+ val = trimmedLine.substring(splitIdx + 1).trim();
+ } else {
+ key = trimmedLine.substring(0, splitIdx).trim();
+ val = trimmedLine.substring(splitIdx).trim();
+ }
+
+ // Check if it's a named table start
+ if (val.startsWith(String.valueOf(Constants.TABLE_MARKER))) {
+ Object[] parsed = parseTableHeader(val);
+ currentTableName = key;
+ currentTable = (TableInfo) parsed[1];
+ tables.put(currentTableName, currentTable);
+ } else {
+ currentTable = null;
+ metadata.put(key, parseValue(val));
+ }
+ }
+ }
+ }
+
+ // Recombine tables into metadata
+ for (Map.Entry entry : tables.entrySet()) {
+ String tableName = entry.getKey();
+ TableInfo table = entry.getValue();
+
+ // Strict mode: validate row count
+ if (strict && table.rows.size() != table.expectedRows) {
+ throw new ZonDecodeError(
+ "[E001] Row count mismatch in table '" + tableName +
+ "': expected " + table.expectedRows + ", got " + table.rows.size(),
+ "E001", null, null, "Table: " + tableName
+ );
+ }
+
+ metadata.put(tableName, reconstructTable(table));
+ }
+
+ // Unflatten dotted keys
+ Object result = unflatten(metadata);
+
+ // Unwrap pure lists
+ if (result instanceof Map) {
+ @SuppressWarnings("unchecked")
+ Map resultMap = (Map) result;
+ if (resultMap.size() == 1 && resultMap.containsKey("data") && resultMap.get("data") instanceof List) {
+ return resultMap.get("data");
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Convenience static method to decode with default settings.
+ *
+ * @param zonStr ZON format string
+ * @return Decoded Java object
+ */
+ public static Object decodeStatic(String zonStr) {
+ return new ZonDecoder().decode(zonStr);
+ }
+
+ /**
+ * Convenience static method to decode with specified strictness.
+ *
+ * @param zonStr ZON format string
+ * @param strict Whether to enable strict validation
+ * @return Decoded Java object
+ */
+ public static Object decodeStatic(String zonStr, boolean strict) {
+ return new ZonDecoder(strict).decode(zonStr);
+ }
+
+ private Object[] parseTableHeader(String line) {
+ // Try v2.0 format with name
+ Matcher m = V2_NAMED_PATTERN.matcher(line);
+ if (m.matches()) {
+ String tableName = m.group(1);
+ int count = Integer.parseInt(m.group(2));
+ String omittedStr = m.group(3) != null ? m.group(3) : "";
+ String colsStr = m.group(4);
+
+ List omittedCols = parseOmittedCols(omittedStr);
+ List cols = parseColumns(colsStr);
+
+ return new Object[]{tableName, new TableInfo(cols, omittedCols, count)};
+ }
+
+ // Try v2.1 format (anonymous/value)
+ m = V2_VALUE_PATTERN.matcher(line);
+ if (m.matches()) {
+ int count = Integer.parseInt(m.group(1));
+ String omittedStr = m.group(2) != null ? m.group(2) : "";
+ String colsStr = m.group(3);
+
+ List omittedCols = parseOmittedCols(omittedStr);
+ List cols = parseColumns(colsStr);
+
+ return new Object[]{"data", new TableInfo(cols, omittedCols, count)};
+ }
+
+ // Try v2.0 format (anonymous)
+ m = V2_PATTERN.matcher(line);
+ if (m.matches()) {
+ int count = Integer.parseInt(m.group(1));
+ String omittedStr = m.group(2) != null ? m.group(2) : "";
+ String colsStr = m.group(3);
+
+ List omittedCols = parseOmittedCols(omittedStr);
+ List cols = parseColumns(colsStr);
+
+ return new Object[]{"data", new TableInfo(cols, omittedCols, count)};
+ }
+
+ // Try v1.x format
+ m = V1_PATTERN.matcher(line);
+ if (m.matches()) {
+ String tableName = m.group(1);
+ int count = Integer.parseInt(m.group(2));
+ String colsStr = m.group(3);
+
+ List cols = parseColumns(colsStr);
+
+ return new Object[]{tableName, new TableInfo(cols, Collections.emptyList(), count)};
+ }
+
+ throw new ZonDecodeError("Invalid table header: " + line);
+ }
+
+ private List parseOmittedCols(String omittedStr) {
+ List cols = new ArrayList<>();
+ if (omittedStr != null && !omittedStr.isEmpty()) {
+ Matcher m = OMITTED_COL_PATTERN.matcher(omittedStr);
+ while (m.find()) {
+ cols.add(m.group(1));
+ }
+ }
+ return cols;
+ }
+
+ private List parseColumns(String colsStr) {
+ String[] parts = colsStr.split(",");
+ List cols = new ArrayList<>();
+ for (String part : parts) {
+ cols.add(part.trim());
+ }
+ return cols;
+ }
+
+ private Map parseTableRow(String line, TableInfo table) {
+ List tokens = splitByDelimiter(line, ',');
+
+ int coreFieldCount = tokens.size();
+ int sparseFieldCount = 0;
+
+ // Count sparse fields
+ for (int i = table.cols.size(); i < tokens.size(); i++) {
+ String tok = tokens.get(i);
+ if (tok.contains(":") && !isURL(tok) && !isTimestamp(tok)) {
+ sparseFieldCount++;
+ }
+ }
+
+ int actualCoreFields = Math.min(coreFieldCount, table.cols.size());
+
+ // Strict mode validation
+ if (strict && coreFieldCount < table.cols.size() && sparseFieldCount == 0) {
+ throw new ZonDecodeError(
+ "[E002] Field count mismatch on row " + (table.rowIndex + 1) +
+ ": expected " + table.cols.size() + " fields, got " + coreFieldCount,
+ "E002", currentLine, null,
+ line.length() > 50 ? line.substring(0, 50) + "..." : line
+ );
+ }
+
+ // Pad if needed
+ while (tokens.size() < table.cols.size()) {
+ tokens.add("");
+ }
+
+ Map row = new LinkedHashMap<>();
+ int tokenIdx = 0;
+
+ // Parse core columns
+ for (String col : table.cols) {
+ if (tokenIdx < tokens.size()) {
+ String tok = tokens.get(tokenIdx);
+ row.put(col, parseValue(tok));
+ tokenIdx++;
+ }
+ }
+
+ // Parse optional fields (sparse encoding)
+ while (tokenIdx < tokens.size()) {
+ String tok = tokens.get(tokenIdx);
+ if (tok.contains(":") && !isURL(tok) && !isTimestamp(tok)) {
+ int colonIdx = tok.indexOf(':');
+ String key = tok.substring(0, colonIdx).trim();
+ String val = tok.substring(colonIdx + 1).trim();
+
+ if (KEY_PATTERN.matcher(key).matches()) {
+ row.put(key, parseValue(val));
+ }
+ }
+ tokenIdx++;
+ }
+
+ // Reconstruct omitted sequential columns
+ if (table.omittedCols != null) {
+ for (String col : table.omittedCols) {
+ row.put(col, table.rowIndex + 1);
+ }
+ }
+
+ table.rowIndex++;
+ return row;
+ }
+
+ private boolean isURL(String s) {
+ return URL_PATTERN.matcher(s).find() || s.startsWith("/");
+ }
+
+ private boolean isTimestamp(String s) {
+ return TIMESTAMP_PATTERN.matcher(s).find() || TIME_PATTERN.matcher(s).find();
+ }
+
+ private List