From c4e0ab361b7f9c1604ca2b67716461d49de12fba Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Tue, 25 Nov 2025 17:42:34 +0100 Subject: [PATCH 1/5] Update specs json files for conformance test --- .../conformance/decode/arrays-nested.json | 27 +++++++++++--- .../conformance/encode/arrays-nested.json | 2 +- .../conformance/encode/arrays-objects.json | 36 ++++++++++++++----- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/test/resources/conformance/decode/arrays-nested.json b/src/test/resources/conformance/decode/arrays-nested.json index 01bbff7..ffd4d5c 100644 --- a/src/test/resources/conformance/decode/arrays-nested.json +++ b/src/test/resources/conformance/decode/arrays-nested.json @@ -1,5 +1,5 @@ { - "version": "1.4", + "version": "3.0", "category": "decode", "description": "Nested and mixed array decoding - list format, arrays of arrays, root arrays, mixed types", "tests": [ @@ -52,8 +52,8 @@ "specSection": "9.4" }, { - "name": "parses nested tabular arrays as first field on hyphen line", - "input": "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob\n status: active", + "name": "parses list items whose first field is a tabular array", + "input": "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob\n status: active", "expected": { "items": [ { @@ -65,7 +65,24 @@ } ] }, - "specSection": "10" + "specSection": "10", + "note": "Canonical encoding: tabular header on hyphen line, rows at depth +2, sibling fields at depth +1" + }, + { + "name": "parses single-field list-item object with tabular array", + "input": "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob", + "expected": { + "items": [ + { + "users": [ + { "id": 1, "name": "Ada" }, + { "id": 2, "name": "Bob" } + ] + } + ] + }, + "specSection": "10", + "note": "Single-field list-item object: only the tabular array, no sibling fields" }, { "name": "parses objects containing arrays (including empty arrays) in list format", @@ -79,7 +96,7 @@ }, { "name": "parses arrays of arrays within objects", - "input": "items[1]:\n - matrix[2]:\n - [2]: 1,2\n - [2]: 3,4\n name: grid", + "input": "items[1]:\n - matrix[2]:\n - [2]: 1,2\n - [2]: 3,4\n name: grid", "expected": { "items": [ { "matrix": [[1, 2], [3, 4]], "name": "grid" } diff --git a/src/test/resources/conformance/encode/arrays-nested.json b/src/test/resources/conformance/encode/arrays-nested.json index e15ab5f..cc79b23 100644 --- a/src/test/resources/conformance/encode/arrays-nested.json +++ b/src/test/resources/conformance/encode/arrays-nested.json @@ -1,5 +1,5 @@ { - "version": "1.4", + "version": "3.0", "category": "encode", "description": "Nested and mixed array encoding - arrays of arrays, mixed type arrays, root arrays", "tests": [ diff --git a/src/test/resources/conformance/encode/arrays-objects.json b/src/test/resources/conformance/encode/arrays-objects.json index 8df4988..d601b9a 100644 --- a/src/test/resources/conformance/encode/arrays-objects.json +++ b/src/test/resources/conformance/encode/arrays-objects.json @@ -1,5 +1,5 @@ { - "version": "1.4", + "version": "3.0", "category": "encode", "description": "Arrays of objects encoding - list format for non-uniform objects and complex structures", "tests": [ @@ -47,7 +47,7 @@ { "matrix": [[1, 2], [3, 4]], "name": "grid" } ] }, - "expected": "items[1]:\n - matrix[2]:\n - [2]: 1,2\n - [2]: 3,4\n name: grid", + "expected": "items[1]:\n - matrix[2]:\n - [2]: 1,2\n - [2]: 3,4\n name: grid", "specSection": "10" }, { @@ -57,8 +57,9 @@ { "users": [{ "id": 1, "name": "Ada" }, { "id": 2, "name": "Bob" }], "status": "active" } ] }, - "expected": "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob\n status: active", - "specSection": "10" + "expected": "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob\n status: active", + "specSection": "10", + "note": "YAML-style encoding for list-item objects with tabular array as first field" }, { "name": "uses list format for nested object arrays with mismatched keys", @@ -67,7 +68,7 @@ { "users": [{ "id": 1, "name": "Ada" }, { "id": 2 }], "status": "active" } ] }, - "expected": "items[1]:\n - users[2]:\n - id: 1\n name: Ada\n - id: 2\n status: active", + "expected": "items[1]:\n - users[2]:\n - id: 1\n name: Ada\n - id: 2\n status: active", "specSection": "10" }, { @@ -97,12 +98,22 @@ "specSection": "10" }, { - "name": "places first field of nested tabular arrays on hyphen line", + "name": "uses canonical encoding for multi-field list-item objects with tabular arrays", "input": { "items": [{ "users": [{ "id": 1 }, { "id": 2 }], "note": "x" }] }, - "expected": "items[1]:\n - users[2]{id}:\n 1\n 2\n note: x", - "specSection": "10" + "expected": "items[1]:\n - users[2]{id}:\n 1\n 2\n note: x", + "specSection": "10", + "note": "Tabular header on hyphen line with rows at depth +2 and sibling fields at depth +1" + }, + { + "name": "uses canonical encoding for single-field list-item tabular arrays", + "input": { + "items": [{ "users": [{ "id": 1, "name": "Ada" }, { "id": 2, "name": "Bob" }] }] + }, + "expected": "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob", + "specSection": "10", + "note": "Tabular header on hyphen line with rows at depth +2" }, { "name": "places empty arrays on hyphen line when first", @@ -112,6 +123,15 @@ "expected": "items[1]:\n - data[0]:\n name: x", "specSection": "10" }, + { + "name": "encodes empty object list items as bare hyphen", + "input": { + "items": ["first", "second", {}] + }, + "expected": "items[3]:\n - first\n - second\n -", + "specSection": "10", + "note": "Empty object list items encode as a single \"-\" line at the list-item depth" + }, { "name": "uses field order from first object for tabular headers", "input": { From f84b6198f9e0bc561ac8a11df7313f95cf4c9ee3 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Tue, 25 Nov 2025 17:46:50 +0100 Subject: [PATCH 2/5] adding test for new specs --- .../java/dev/toonformat/jtoon/JToonTest.java | 18 +- .../jtoon/decoder/ValueDecoderTest.java | 27 +++ .../jtoon/encoder/ListItemEncoderTest.java | 55 +++++++ .../jtoon/encoder/ObjectEncoderTest.java | 154 +++++++++++------- 4 files changed, 182 insertions(+), 72 deletions(-) diff --git a/src/test/java/dev/toonformat/jtoon/JToonTest.java b/src/test/java/dev/toonformat/jtoon/JToonTest.java index f3e659b..92196fa 100644 --- a/src/test/java/dev/toonformat/jtoon/JToonTest.java +++ b/src/test/java/dev/toonformat/jtoon/JToonTest.java @@ -450,8 +450,8 @@ void usesListForArrayOfArrays() { """ items[1]: - matrix[2]: - - [2]: 1,2 - - [2]: 3,4 + - [2]: 1,2 + - [2]: 3,4 name: grid""", encode(obj)); } @@ -467,8 +467,8 @@ void usesTabularForNestedUniformArrays() { """ items[1]: - users[2]{id,name}: - 1,Ada - 2,Bob + 1,Ada + 2,Bob status: active""", encode(obj)); } @@ -483,9 +483,9 @@ void usesListForMismatchedKeys() { """ items[1]: - users[2]: - - id: 1 - name: Ada - - id: 2 + - id: 1 + name: Ada + - id: 2 status: active""", encode(obj)); } @@ -538,8 +538,8 @@ void placesTabularOnHyphenLine() { """ items[1]: - users[2]{id}: - 1 - 2 + 1 + 2 note: x""", encode(obj)); } diff --git a/src/test/java/dev/toonformat/jtoon/decoder/ValueDecoderTest.java b/src/test/java/dev/toonformat/jtoon/decoder/ValueDecoderTest.java index fb5ef78..19fe64a 100644 --- a/src/test/java/dev/toonformat/jtoon/decoder/ValueDecoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/decoder/ValueDecoderTest.java @@ -1,5 +1,6 @@ package dev.toonformat.jtoon.decoder; +import dev.toonformat.jtoon.DecodeOptions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -29,4 +30,30 @@ void throwsOnConstructor() throws NoSuchMethodException { assertInstanceOf(UnsupportedOperationException.class, cause); assertEquals("Utility class cannot be instantiated", cause.getMessage()); } + + @Test + @DisplayName("parses list items whose first field is a tabular array") + void decodeTabularArray() { + // Given + String input = "items[1]:\n - users[2]{id,name}:\n 1,Ada\n 2,Bob\n status: active"; + + // When + String result = ValueDecoder.decodeToJson(input, DecodeOptions.DEFAULT); + + // Then + assertEquals("{\"items\":[{\"users\":[{\"id\":1,\"name\":\"Ada\"},{\"id\":2,\"name\":\"Bob\"}],\"status\":\"active\"}]}", result); + } + + @Test + @DisplayName("parses arrays of arrays within objects") + void decodeArraysOfArraysWithinObjects() { + // Given + String input = "items[1]:\n - matrix[2]:\n - [2]: 1,2\n - [2]: 3,4\n name: grid"; + + // When + String result = ValueDecoder.decodeToJson(input, DecodeOptions.DEFAULT); + + // Then + assertEquals("{\"items\":[{\"matrix\":[[1,2],[3,4]],\"name\":\"grid\"}]}", result); + } } diff --git a/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java index 57a4d66..076d186 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java @@ -3,12 +3,15 @@ import dev.toonformat.jtoon.EncodeOptions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.JsonNodeFactory; import tools.jackson.databind.node.ObjectNode; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; @@ -111,4 +114,56 @@ void givenMultipleFields_whenEncoded_thenRemainingFieldsAreDelegated() { assertEquals("- a: 1\n" + " b: 2", writer.toString()); } + + @Test + void usesTabularFormatForNestedUniformObjectArrays() { + // Given + String json = "[\n" + + " { \"users\": [{ \"id\": 1, \"name\": \"Ada\" }, { \"id\": 2, \"name\": \"Bob\" }], \"status\": \"active\" }\n" + + " ]"; + ArrayNode node = (ArrayNode) new ObjectMapper().readTree(json); + + EncodeOptions options = EncodeOptions.DEFAULT; + LineWriter writer = new LineWriter(options.indent()); + Set rootKeys = new HashSet<>(); + + // When + ArrayEncoder.encodeArray("items",node, writer, 0, options); + + // Then + String expected = String.join("\n", + "items[1]:", + " - users[2]{id,name}:", + " 1,Ada", + " 2,Bob", + " status: active"); + assertEquals(expected, writer.toString()); + } + + @Test + void usesListFormatForNestedObjectArraysWithMismatchedKeys() { + // Given + String json = "[\n" + + " { \"users\": [{ \"id\": 1, \"name\": \"Ada\" }, { \"id\": 2 }], \"status\": \"active\" }\n" + + " ]"; + ArrayNode node = (ArrayNode) new ObjectMapper().readTree(json); + + EncodeOptions options = EncodeOptions.DEFAULT; + LineWriter writer = new LineWriter(options.indent()); + Set rootKeys = new HashSet<>(); + + // When + ArrayEncoder.encodeArray("items", node, writer, 0, options); + + + // Then + String expected = String.join("\n", + "items[1]:", + " - users[2]:", + " - id: 1", + " name: Ada", + " - id: 2", + " status: active"); + assertEquals(expected, writer.toString()); + } } \ No newline at end of file diff --git a/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java index fa67b0b..e3bb83c 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java @@ -19,6 +19,7 @@ * Test for the ObjectEncoder */ class ObjectEncoderTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); @Test @@ -53,8 +54,8 @@ void givenNestedObjectAndFlattenOff_whenEncoding_thenWritesIndentedBlocks() { // Then assertEquals(""" - x: - y: ok""", writer.toString()); + x: + y: ok""", writer.toString()); } @Test @@ -92,9 +93,9 @@ void givenPartiallyFoldableKeyChain_whenRemainingDepthTooSmall_thenFlattenStops( // Then assertEquals(""" - a: - b: - z: 1""", writer.toString()); + a: + b: + z: 1""", writer.toString()); } @Test @@ -184,19 +185,18 @@ void givenPartiallyFoldedKeyChain_whenFoldResultHasRemainder_thenEncodesCase2Pat LineWriter writer = new LineWriter(options.indent()); Set rootKeys = new HashSet<>(); - // When ObjectEncoder.encodeObject(node, writer, 0, options, rootKeys, null, null, new HashSet<>()); // Then assertEquals(""" - items[3]: - - summary - - id: 1 - name: Ada - - [2]: - - id: 2 - - status: draft""", writer.toString()); + items[3]: + - summary + - id: 1 + name: Ada + - [2]: + - id: 2 + - status: draft""", writer.toString()); } @Test @@ -206,7 +206,7 @@ void throwsOnConstructor() throws NoSuchMethodException { constructor.setAccessible(true); final InvocationTargetException thrown = - assertThrows(InvocationTargetException.class, constructor::newInstance); + assertThrows(InvocationTargetException.class, constructor::newInstance); final Throwable cause = thrown.getCause(); assertInstanceOf(UnsupportedOperationException.class, cause); @@ -223,39 +223,39 @@ void givenPrimitiveLeaf_whenFlatten_thenWriterReceivesEncodedLine() throws Excep Set rootLiteralKeys = new HashSet<>(); Set blockedKeys = new HashSet<>(); Flatten.FoldResult fullFold = new Flatten.FoldResult( - "a.b", // foldedKey - null, // remainder - MAPPER.readTree("1"), // leafValue - 1 // segmentCount + "a.b", // foldedKey + null, // remainder + MAPPER.readTree("1"), // leafValue + 1 // segmentCount ); // Access private method Method flattenMethod = ObjectEncoder.class.getDeclaredMethod( - "flatten", - String.class, - Flatten.FoldResult.class, - LineWriter.class, - int.class, - EncodeOptions.class, - Set.class, - String.class, - Set.class, - int.class + "flatten", + String.class, + Flatten.FoldResult.class, + LineWriter.class, + int.class, + EncodeOptions.class, + Set.class, + String.class, + Set.class, + int.class ); flattenMethod.setAccessible(true); // when Object returnValue = flattenMethod.invoke( - null, // static method - key, - fullFold, - writer, - 0, - options, - rootLiteralKeys, - null, - blockedKeys, - 5 + null, // static method + key, + fullFold, + writer, + 0, + options, + rootLiteralKeys, + null, + blockedKeys, + 5 ); // then @@ -263,7 +263,7 @@ void givenPrimitiveLeaf_whenFlatten_thenWriterReceivesEncodedLine() throws Excep assertEquals(1, writer.toString().lines().count(), "Writer should contain one line"); String line = writer.toString(); - assertEquals("a.b: 1",line ); + assertEquals("a.b: 1", line); assertEquals(2, blockedKeys.size()); assertTrue(blockedKeys.contains("a")); @@ -283,39 +283,39 @@ void givenPartiallyFolded_whenFlatten_thenWriterReceivesFoldedKeyAndObjectIsEnco ObjectNode remainderNode = (ObjectNode) MAPPER.readTree("{\"c\": 5}"); Flatten.FoldResult partialFold = new Flatten.FoldResult( - "a.b", - remainderNode, - null, - 1 + "a.b", + remainderNode, + null, + 1 ); // Access private method Method flattenMethod = ObjectEncoder.class.getDeclaredMethod( - "flatten", - String.class, - Flatten.FoldResult.class, - LineWriter.class, - int.class, - EncodeOptions.class, - Set.class, - String.class, - Set.class, - int.class + "flatten", + String.class, + Flatten.FoldResult.class, + LineWriter.class, + int.class, + EncodeOptions.class, + Set.class, + String.class, + Set.class, + int.class ); flattenMethod.setAccessible(true); // when Object result = flattenMethod.invoke( - null, // static - key, // "a" - partialFold, // {"b":{"c":5}} - writer, - 0, // depth - options, - rootLiteralKeys, - null, // pathPrefix - blockedKeys, - 1 // remainingDepth (will go to <=0, disable flattening) + null, // static + key, // "a" + partialFold, // {"b":{"c":5}} + writer, + 0, // depth + options, + rootLiteralKeys, + null, // pathPrefix + blockedKeys, + 1 // remainingDepth (will go to <=0, disable flattening) ); // then @@ -325,4 +325,32 @@ void givenPartiallyFolded_whenFlatten_thenWriterReceivesFoldedKeyAndObjectIsEnco assertTrue(blockedKeys.contains("a"), "Original key should be blocked"); assertTrue(blockedKeys.contains("a.b"), "Folded key should be blocked"); } + + @Test + void usesListFormatForObjectsContainingArraysOfArrays() { + // Given + String json = "{\n" + + " \"items\": [\n" + + " { \"matrix\": [[1, 2], [3, 4]], \"name\": \"grid\" }\n" + + " ]\n" + + " }"; + ObjectNode node = (ObjectNode) new ObjectMapper().readTree(json); + + EncodeOptions options = EncodeOptions.withFlatten(true); + LineWriter writer = new LineWriter(options.indent()); + Set rootKeys = new HashSet<>(); + + // When + ObjectEncoder.encodeObject(node, writer, 0, options, rootKeys, null, null, new HashSet<>()); + + // Then + String expected = String.join("\n", + "items[1]:", + " - matrix[2]:", + " - [2]: 1,2", + " - [2]: 3,4", + " name: grid"); + assertEquals(expected, writer.toString()); + } + } \ No newline at end of file From e937a75a53aa93a8ce9b3796f08422ee5dd7af34 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Tue, 25 Nov 2025 17:54:32 +0100 Subject: [PATCH 3/5] code change for spec 3.0 --- .../jtoon/decoder/ValueDecoder.java | 64 ++++++++++++------- .../jtoon/encoder/ListItemEncoder.java | 12 ++-- .../jtoon/encoder/ObjectEncoder.java | 2 +- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java index d954590..c55ce0a 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java @@ -391,8 +391,21 @@ private List parseTabularArray(String header, int depth, String arrayDel List result = new ArrayList<>(); currentLine++; + // Determine the expected row depth dynamically from the first non-blank line + int expectedRowDepth; + if (currentLine < lines.length) { + int nextNonBlankLine = findNextNonBlankLine(currentLine); + if (nextNonBlankLine < lines.length) { + expectedRowDepth = getDepth(lines[nextNonBlankLine]); + } else { + expectedRowDepth = depth + 1; + } + } else { + expectedRowDepth = depth + 1; + } + while (currentLine < lines.length) { - if (!processTabularArrayLine(depth, keys, arrayDelimiter, result)) { + if (!processTabularArrayLine(expectedRowDepth, keys, arrayDelimiter, result)) { break; } } @@ -403,42 +416,44 @@ private List parseTabularArray(String header, int depth, String arrayDel /** * Processes a single line in a tabular array. - * Returns true if parsing should continue, false if array should terminate. + * Returns true if parsing should continue, false if an array should terminate. */ - private boolean processTabularArrayLine(int depth, List keys, String arrayDelimiter, + private boolean processTabularArrayLine(int expectedRowDepth, List keys, String arrayDelimiter, List result) { String line = lines[currentLine]; if (isBlankLine(line)) { - return !handleBlankLineInTabularArray(depth); + return !handleBlankLineInTabularArray(expectedRowDepth); } int lineDepth = getDepth(line); - if (shouldTerminateTabularArray(line, lineDepth, depth)) { + if (shouldTerminateTabularArray(line, lineDepth, expectedRowDepth)) { return false; } - if (processTabularRow(line, lineDepth, depth, keys, arrayDelimiter, result)) { + if (processTabularRow(line, lineDepth, expectedRowDepth, keys, arrayDelimiter, result)) { currentLine++; } return true; } /** - * Handles blank line processing in tabular array. - * Returns true if array should terminate, false if line should be skipped. + * Handles blank line processing in a tabular array. + * Returns true if an array should terminate, false if a line should be skipped. */ - private boolean handleBlankLineInTabularArray(int depth) { + private boolean handleBlankLineInTabularArray(int expectedRowDepth) { int nextNonBlankLine = findNextNonBlankLine(currentLine + 1); if (nextNonBlankLine < lines.length) { int nextDepth = getDepth(lines[nextNonBlankLine]); - if (nextDepth <= depth) { - return true; // Blank line is outside array - terminate + // Header depth is one level above the expected row depth + int headerDepth = expectedRowDepth - 1; + if (nextDepth <= headerDepth) { + return true; } } - // Blank line is inside array + // Blank line is inside the array if (options.strict()) { throw new IllegalArgumentException( "Blank line inside tabular array at line " + (currentLine + 1)); @@ -463,10 +478,13 @@ private int findNextNonBlankLine(int startIndex) { * Determines if tabular array parsing should terminate based online depth. * Returns true if array should terminate, false otherwise. */ - private boolean shouldTerminateTabularArray(String line, int lineDepth, int depth) { - if (lineDepth < depth + 1) { - if (lineDepth == depth) { - String content = line.substring(depth * options.indent()); + private boolean shouldTerminateTabularArray(String line, int lineDepth, int expectedRowDepth) { + // Header depth is one level above expected row depth + int headerDepth = expectedRowDepth - 1; + + if (lineDepth < expectedRowDepth) { + if (lineDepth == headerDepth) { + String content = line.substring(headerDepth * options.indent()); int colonIdx = findUnquotedColon(content); if (colonIdx > 0) { return true; // Key-value pair at same depth - terminate array @@ -476,8 +494,8 @@ private boolean shouldTerminateTabularArray(String line, int lineDepth, int dept } // Check for key-value pair at expected row depth - if (lineDepth == depth + 1) { - String rowContent = line.substring((depth + 1) * options.indent()); + if (lineDepth == expectedRowDepth) { + String rowContent = line.substring(expectedRowDepth * options.indent()); int colonIdx = findUnquotedColon(rowContent); return colonIdx > 0; // Key-value pair at same depth as rows - terminate array } @@ -490,14 +508,14 @@ private boolean shouldTerminateTabularArray(String line, int lineDepth, int dept * Returns true if line was processed and currentLine should be incremented, * false otherwise. */ - private boolean processTabularRow(String line, int lineDepth, int depth, List keys, + private boolean processTabularRow(String line, int lineDepth, int expectedRowDepth, List keys, String arrayDelimiter, List result) { - if (lineDepth == depth + 1) { - String rowContent = line.substring((depth + 1) * options.indent()); + if (lineDepth == expectedRowDepth) { + String rowContent = line.substring(expectedRowDepth * options.indent()); Map row = parseTabularRow(rowContent, keys, arrayDelimiter); result.add(row); return true; - } else if (lineDepth > depth + 1) { + } else if (lineDepth > expectedRowDepth) { // Line is deeper than expected - might be nested content, skip it currentLine++; return false; @@ -636,7 +654,7 @@ private Object parseListItem(String content, int depth) { // For nested arrays in list items, default to comma delimiter if not specified String nestedArrayDelimiter = extractDelimiterFromHeader(arrayHeader); - var arrayValue = parseArrayWithDelimiter(arrayHeader, depth + 1, nestedArrayDelimiter); + List arrayValue = parseArrayWithDelimiter(arrayHeader, depth + 2, nestedArrayDelimiter); Map item = new LinkedHashMap<>(); item.put(key, arrayValue); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java index ef77084..bc905ba 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java @@ -25,7 +25,7 @@ private ListItemEncoder() { /** * Encodes an object as a list item. - * First key-value appears on the "- " line, remaining fields are indented. + * The first key-value appears on the "- " line, remaining fields are indented. * * @param obj The object to encode * @param writer LineWriter for output @@ -101,13 +101,13 @@ private static void encodeFirstArrayAsObjects(String key, String encodedKey, Arr options.delimiter().getValue(), options.lengthMarker()); writer.push(depth, LIST_ITEM_PREFIX + headerStr); // Write just the rows, header was already written above - TabularArrayEncoder.writeTabularRows(arrayValue, header, writer, depth + 1, options); + TabularArrayEncoder.writeTabularRows(arrayValue, header, writer, depth + 2, options); } else { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + OPEN_BRACKET + arrayValue.size() + CLOSE_BRACKET + COLON); for (JsonNode item : arrayValue) { if (item.isObject()) { - encodeObjectAsListItem((ObjectNode) item, writer, depth + 1, options); + encodeObjectAsListItem((ObjectNode) item, writer, depth + 2, options); } } } @@ -119,14 +119,14 @@ private static void encodeFirstArrayAsComplex(String encodedKey, ArrayNode array for (JsonNode item : arrayValue) { if (item.isValueNode()) { - writer.push(depth + 1, LIST_ITEM_PREFIX + writer.push(depth + 2, LIST_ITEM_PREFIX + PrimitiveEncoder.encodePrimitive(item, options.delimiter().getValue())); } else if (item.isArray() && ArrayEncoder.isArrayOfPrimitives(item)) { String inline = ArrayEncoder.formatInlineArray((ArrayNode) item, options.delimiter().getValue(), null, options.lengthMarker()); - writer.push(depth + 1, LIST_ITEM_PREFIX + inline); + writer.push(depth + 2, LIST_ITEM_PREFIX + inline); } else if (item.isObject()) { - encodeObjectAsListItem((ObjectNode) item, writer, depth + 1, options); + encodeObjectAsListItem((ObjectNode) item, writer, depth + 2, options); } } } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index 9f159ca..311f8b0 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -132,7 +132,7 @@ public static void encodeKeyValuePair(String key, * @param remainingDepth the depth that remind to the limit * @return EncodeOptions changes for Case 2 */ - private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, LineWriter writer, int depth, EncodeOptions options,Set rootLiteralKeys, String pathPrefix, Set blockedKeys, int remainingDepth) { + private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, LineWriter writer, int depth, EncodeOptions options, Set rootLiteralKeys, String pathPrefix, Set blockedKeys, int remainingDepth) { String foldedKey = foldResult.foldedKey(); // prevent second folding pass From 9e5600bfac2b0c8dcc9db1757fb5536e9ba99046 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Tue, 25 Nov 2025 17:55:09 +0100 Subject: [PATCH 4/5] adding git attributes for line endings CRLF --- .gitattributes | 1 + .editorconfig | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 .gitattributes create mode 100644 .editorconfig diff --git a/ .gitattributes b/ .gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/ .gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..45f8e4e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 \ No newline at end of file From a820dbeb9bca185b4f9a5ca8b9bbab49588f251f Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Tue, 25 Nov 2025 18:33:45 +0100 Subject: [PATCH 5/5] use CRLF for the windows batch script --- .gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ .gitattributes b/ .gitattributes index 94f480d..4b887bf 100644 --- a/ .gitattributes +++ b/ .gitattributes @@ -1 +1,2 @@ -* text=auto eol=lf \ No newline at end of file +* text=auto eol=lf +*.cmd text eol=crlf