Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
aa7dcd3
feat: create test in order to check JToon.encodeJson
Invokedzz Nov 9, 2025
8b9b427
Merge remote-tracking branch 'origin/main'
Invokedzz Nov 9, 2025
412ca82
Merge pull request #22 from Invokedzz/main
felipestanzani Nov 9, 2025
191b713
Merge branch 'main' into conformance
felipestanzani Nov 10, 2025
995603e
Refactoring Conformance tests
felipestanzani Nov 10, 2025
1216d2e
Enhance Conformance tests with dynamic test generation and improved o…
felipestanzani Nov 10, 2025
73bca45
Refactor JToon and ValueDecoder for improved clarity and performance
felipestanzani Nov 11, 2025
2917b51
- Enhanced whitespace tolerance and error handling in decoding proces…
felipestanzani Nov 11, 2025
dc7aacc
Refactor test project structure
felipestanzani Nov 11, 2025
15a6f0b
WIP Implement encoding and decoding conformance tests with new model …
felipestanzani Nov 11, 2025
2e3e433
WIP Enhance Conformance tests with improved error handling and refact…
felipestanzani Nov 11, 2025
299665e
WIP Enhance DecodeOptions and ValueDecoder for path expansion support
felipestanzani Nov 11, 2025
61ea8e8
WIP Update project SDK version and enhance ValueDecoder with improved…
felipestanzani Nov 11, 2025
e610444
WIP Refactor ValueDecoder for improved parsing and error handling
felipestanzani Nov 11, 2025
25878c6
WIP Enhance ValueDecoder parsing logic and update tests for empty inp…
felipestanzani Nov 11, 2025
65777c5
Refactor ValueDecoder to improve whitespace handling and indentation …
felipestanzani Nov 11, 2025
36029a7
Refactor ConformanceTest to utilize DynamicContainer for test organiz…
felipestanzani Nov 11, 2025
a5ab04d
All the conformance tests are passing
felipestanzani Nov 11, 2025
6345301
Update dependabot configuration to change update schedule from weekly…
felipestanzani Nov 12, 2025
6c6dee3
Refactor ValueDecoder to streamline parsing logic and improve error h…
felipestanzani Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
open-pull-requests-limit: 10
groups:
test:
Expand All @@ -13,4 +13,4 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions .idea/sonarlint.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sonar.java.source=17
16 changes: 9 additions & 7 deletions src/main/java/com/felipestanzani/jtoon/DecodeOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,46 @@
* IllegalArgumentException on invalid input. When false,
* uses best-effort parsing and returns null on errors
* (default: true)
* @param expandPaths Path expansion mode for dotted keys (default: OFF)
*/
public record DecodeOptions(
int indent,
Delimiter delimiter,
boolean strict) {
boolean strict,
PathExpansion expandPaths) {
/**
* Default decoding options: 2 spaces indent, comma delimiter, strict validation
* Default decoding options: 2 spaces indent, comma delimiter, strict validation, path expansion off
*/
public static final DecodeOptions DEFAULT = new DecodeOptions(2, Delimiter.COMMA, true);
public static final DecodeOptions DEFAULT = new DecodeOptions(2, Delimiter.COMMA, true, PathExpansion.OFF);

/**
* Creates DecodeOptions with default values.
*/
public DecodeOptions() {
this(2, Delimiter.COMMA, true);
this(2, Delimiter.COMMA, true, PathExpansion.OFF);
}

/**
* Creates DecodeOptions with custom indent, using default delimiter and strict
* mode.
*/
public static DecodeOptions withIndent(int indent) {
return new DecodeOptions(indent, Delimiter.COMMA, true);
return new DecodeOptions(indent, Delimiter.COMMA, true, PathExpansion.OFF);
}

/**
* Creates DecodeOptions with custom delimiter, using default indent and strict
* mode.
*/
public static DecodeOptions withDelimiter(Delimiter delimiter) {
return new DecodeOptions(2, delimiter, true);
return new DecodeOptions(2, delimiter, true, PathExpansion.OFF);
}

/**
* Creates DecodeOptions with custom strict mode, using default indent and
* delimiter.
*/
public static DecodeOptions withStrict(boolean strict) {
return new DecodeOptions(2, Delimiter.COMMA, strict);
return new DecodeOptions(2, Delimiter.COMMA, strict, PathExpansion.OFF);
}
}
38 changes: 1 addition & 37 deletions src/main/java/com/felipestanzani/jtoon/JToon.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,9 @@
import com.felipestanzani.jtoon.encoder.ValueEncoder;
import com.felipestanzani.jtoon.normalizer.JsonNormalizer;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;

/**
* Main API for encoding and decoding JToon format.
*
* <p>
* JToon is a structured text format that represents JSON-like data in a more
* human-readable way, with support for tabular arrays and inline formatting.
* </p>
*
* <h2>Usage Examples:</h2>
*
* <pre>{@code
* // Encode a Java object with default options
* String toon = JToon.encode(myObject);
*
* // Encode with custom options
* EncodeOptions options = new EncodeOptions(4, Delimiter.PIPE, true);
* String toon = JToon.encode(myObject, options);
*
* // Encode a plain JSON string directly
* String toon = JToon.encodeJson("{\"id\":123,\"name\":\"Ada\"}");
*
* // Decode TOON back to Java objects
* Object result = JToon.decode(toon);
*
* // Decode TOON directly to JSON string
* String json = JToon.decodeToJson(toon);
* }</pre>
*/
public final class JToon {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private JToon() {
throw new UnsupportedOperationException("Utility class cannot be instantiated");
}
Expand Down Expand Up @@ -179,11 +148,6 @@ public static String decodeToJson(String toon) {
* invalid
*/
public static String decodeToJson(String toon, DecodeOptions options) {
try {
Object decoded = ValueDecoder.decode(toon, options);
return OBJECT_MAPPER.writeValueAsString(decoded);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to convert decoded value to JSON: " + e.getMessage(), e);
}
return ValueDecoder.decodeToJson(toon, options);
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/felipestanzani/jtoon/PathExpansion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.felipestanzani.jtoon;

/**
* Path expansion mode for decoding dotted keys.
*/
public enum PathExpansion {
/**
* Safe mode: expands dotted keys like "a.b.c" into nested objects.
* Only expands keys that are valid identifier segments.
*/
SAFE,

/**
* Off mode: treats dotted keys as literal keys.
*/
OFF
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
* Converts TOON scalar representations to appropriate Java types:
* </p>
* <ul>
* <li>{@code "null"} → {@code null}</li>
* <li>{@code "true"} / {@code "false"} → {@code Boolean}</li>
* <li>Numeric strings → {@code Long} or {@code Double}</li>
* <li>Quoted strings → {@code String} (with unescaping)</li>
* <li>Bare strings → {@code String}</li>
* <li>{@code "null"} → {@code null}</li>
* <li>{@code "true"} / {@code "false"} → {@code Boolean}</li>
* <li>Numeric strings → {@code Long} or {@code Double}</li>
* <li>Quoted strings → {@code String} (with unescaping)</li>
* <li>Bare strings → {@code String}</li>
* </ul>
*
* <h2>Examples:</h2>
*
* <pre>{@code
* parse("null") → null
* parse("true") → true
Expand Down Expand Up @@ -46,27 +47,52 @@ static Object parse(String value) {
}

// Check for null literal
if ("null".equals(value)) {
return null;
}

// Check for boolean literals
if ("true".equals(value)) {
return true;
}
if ("false".equals(value)) {
return false;
switch (value) {
case "null" -> {
return null;
}
case "true" -> {
return true;
}
case "false" -> {
return false;
}
default -> {
// Do nothing, continue to next check
}
}

// Check for quoted strings
if (value.startsWith("\"") && value.endsWith("\"")) {
if (value.startsWith("\"")) {
// Validate string before unescaping
StringEscaper.validateString(value);
return StringEscaper.unescape(value);
}

// Check for leading zeros (treat as string, except for "0", "-0", "0.0", etc.)
String trimmed = value.trim();
if (trimmed.length() > 1 && trimmed.matches("^-?0+[\\d].*")
&& !trimmed.matches("^-?0+(\\.0+)?([eE][+-]?\\d+)?$")) {
return value;
}

// Try parsing as number
try {
// Check if it contains exponent notation or decimal point
if (value.contains(".") || value.contains("e") || value.contains("E")) {
return Double.parseDouble(value);
double parsed = Double.parseDouble(value);
// Handle negative zero - Java doesn't distinguish, but spec says it should be 0
if (parsed == 0.0) {
return 0L;
}
// Check if the result is a whole number - if so, return as Long
if (parsed == Math.floor(parsed)
&& !Double.isInfinite(parsed)
&& parsed >= Long.MIN_VALUE
&& parsed <= Long.MAX_VALUE)
return (long) parsed;

return parsed;
} else {
return Long.parseLong(value);
}
Expand Down
Loading