Multi-Format Data Converter (java, written by Codex)
envgap__codex__java-t1-8
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/java-t1 #8 · read the task the agent was given
Codex wrote this java project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.
Task given to the agent:
TASK: Multi-Format Data Converter
Write a program that converts data files between CSV, JSON, XML, and YAML formats while preserving data types, nested structures, and handling large files efficiently.
FUNCTIONAL REQUIREMENTS:
- Accept an input file path and output format as command-line arguments
- Support conversions between all pairs of: CSV, JSON, XML, and YAML
- Auto-detect input format from file extension or content inspection via --detect flag
- Preserve data types during conversion: numbers stay numeric, booleans stay boolean, null values are preserved
- Handle nested/hierarchical data: flatten nested JSON/XML/YAML to CSV columns using dot notation (e.g., address.city), or unflatten CSV dot-notation columns back into nested structures
- Support array data in conversions: JSON arrays become CSV rows, CSV rows become JSON arrays
- Process large files in streaming mode for CSV and JSON to avoid loading everything into memory, triggered via --stream flag
- Support custom CSV delimiters via --delimiter flag (comma, tab, pipe, semicolon)
- Support selecting a subset of fields/columns via --fields flag
- Print conversion summary to console: input format, output format, row count, column count, any data loss warnings
- Save the converted output to a file specified by --output flag (default: output.{format})
- If no input file is given, generate a sample dataset with nested objects, arrays, mixed types, and null values in JSON format, then convert it to all other formats
- Handle encoding differences (UTF-8, Latin-1) and BOM markers gracefully
Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tmlr.codex_generated.p08</groupId>
<artifactId>multi-format-data-converter</artifactId>
<version>1.0.0</version>
<name>Multi-Format Data Converter</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
<version>6.7.0</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>MultiFormatDataConverter</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Multi-Format Data Converter (Java) Converts between `CSV`, `JSON`, `XML`, and `YAML` with nested flatten/unflatten support, type preservation, field filtering, and stream-mode parsing for CSV/NDJSON. ## Requirements - Ubuntu 22.04 - JDK 17+ - Maven 3.9+ ## Dependencies - Direct (all pinned): - `com.fasterxml.jackson.core:jackson-core:2.18.2` - `com.fasterxml.jackson.core:jackson-annotations:2.18.2` - `com.fasterxml.jackson.core:jackson-databind:2.18.2` - `com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.2` - `com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2` - `com.fasterxml.woodstox:woodstox-core:6.7.0` - `org.yaml:snakeyaml:2.3` ## Build ```bash mvn -q -DskipTests compile ``` ## Run General form: ```bash mvn -q exec:java -Dexec.args="<input-file> <output-format> [--output file] [--detect] [--stream] [--delimiter comma|tab|pipe|semicolon] [--fields a,b,c]" ``` Examples: ```bash mvn -q exec:java -Dexec.args="./data.json csv --output out.csv --fields id,name,address.city" mvn -q exec:java -Dexec.args="./data.csv json --delimiter semicolon --detect" mvn -q exec:java -Dexec.args="./data.ndjson yaml --stream" ``` No input (sample generation + conversion to all other formats): ```bash mvn -q exec:java ```
src/main/java/MultiFormatDataConverter.java
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MultiFormatDataConverter {
private static final ObjectMapper JSON = new ObjectMapper();
private static final ObjectMapper YAML = new YAMLMapper();
private static final XmlMapper XML = new XmlMapper();
private static final Set<String> FORMATS = Set.of("csv", "json", "xml", "yaml");
public static void main(String[] args) {
try {
ParsedArgs parsed = parseArgs(args);
if (parsed.positional.isEmpty()) {
runSampleMode(parsed.options);
return;
}
Path inputPath = Paths.get(parsed.positional.get(0)).toAbsolutePath();
if (!Files.exists(inputPath)) {
System.err.println("Input file not found: " + inputPath);
System.exit(1);
}
String outputFormat = "";
if (parsed.positional.size() >= 2) outputFormat = parsed.positional.get(1).toLowerCase();
if (outputFormat.isEmpty()) outputFormat = parsed.options.getOrDefault("format", "").toLowerCase();
if (!FORMATS.contains(outputFormat)) {
System.err.println("Provide output format as positional or --format (csv|json|xml|yaml).");
System.exit(1);
}
convertSingle(inputPath, outputFormat, parsed.options);
} catch (Exception e) {
System.err.println("Conversion failed: " + e.getMessage());
System.exit(1);
}
}
private record ParsedArgs(Map<String, String> options, List<String> positional) {}
private static ParsedArgs parseArgs(String[] args) {
Map<String, String> options = new HashMap<>();
List<String> positional = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
String token = args[i];
if (token.startsWith("--")) {
String key = token.substring(2);
if (i + 1 < args.length && !args[i + 1].startsWith("--")) options.put(key, args[++i]);
else options.put(key, "true");
} else {
positional.add(token);
}
}
return new ParsedArgs(options, positional);
}
private static String normalizeDelimiter(String value) {
if (value == null || value.isBlank() || value.equals(",") || value.equals("comma")) return ",";
if (value.equals("\\t") || value.equals("tab")) return "\t";
if (value.equals("|") || value.equals("pipe")) return "|";
if (value.equals(";") || value.equals("semicolon")) return ";";
return String.valueOf(value.charAt(0));
}
private static String decodeWithFallback(byte[] bytes) {
CharsetDecoder dec = StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
try {
return stripBom(dec.decode(ByteBuffer.wrap(bytes)).toString());
} catch (CharacterCodingException e) {
return stripBom(new String(bytes, StandardCharsets.ISO_8859_1));
}
}
private static String detectEncoding(byte[] bytes) {
CharsetDecoder dec = StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
try {
dec.decode(ByteBuffer.wrap(bytes));
return "utf-8";
} catch (CharacterCodingException e) {
return "latin1";
}
}
private static String stripBom(String text) {
return text.startsWith("\uFEFF") ? text.substring(1) : text;
}
private static String detectInputFormat(Path inputPath, String text, boolean detect) {
if (!detect) {
String ext = extension(inputPath);
if (ext.equals("csv")) return "csv";
if (ext.equals("json") || ext.equals("jsonl") || ext.equals("ndjson")) return "json";
if (ext.equals("xml")) return "xml";
if (ext.equals("yaml") || ext.equals("yml")) return "yaml";
}
String trimmed = text.trim();
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.lines().allMatch(l -> l.isBlank() || l.trim().startsWith("{")))
return "json";
if (trimmed.startsWith("<")) return "xml";
if (!trimmed.isBlank() && trimmed.split("\\R", 2)[0].contains(":")) return "yaml";
return "csv";
}
private static String extension(Path path) {
String name = path.getFileName().toString();
int idx = name.lastIndexOf('.');
return idx >= 0 ? name.substring(idx + 1).toLowerCase() : "";
}
private static Object parsePrimitive(String value) {
if (value == null) return null;
String s = value.trim();
if (s.isEmpty() || s.equalsIgnoreCase("null")) return null;
if (s.equalsIgnoreCase("true")) return true;
if (s.equalsIgnoreCase("false")) return false;
if (s.matches("^[-+]?\\d+$")) return Long.parseLong(s);
if (s.matches("^[-+]?\\d+\\.\\d+$")) return Double.parseDouble(s);
return value;
}
private static List<String> parseCsvLine(String line, char delimiter) {
List<String> out = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char ch = line.charAt(i);
if (ch == '"') {
if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') {
current.append('"');
i++;
} else {
inQuotes = !inQuotes;
}
} else if (ch == delimiter && !inQuotes) {
out.add(current.toString());
current.setLength(0);
} else {
current.append(ch);
}
}
out.add(current.toString());
return out;
}
private static String csvEscape(Object value, char delimiter) {
String s = value == null ? "" : String.valueOf(value);
if (s.indexOf(delimiter) >= 0 || s.contains("\"") || s.contains("\n")) {
return "\"" + s.replace("\"", "\"\"") + "\"";
}
return s;
}
@SuppressWarnings("unchecked")
private static void flattenObject(Object value, String prefix, Map<String, Object> out) {
if (value == null) {
if (!prefix.isEmpty()) out.put(prefix, null);
return;
}
if (value instanceof List<?> list) {
if (list.isEmpty() && !prefix.isEmpty()) out.put(prefix, Collections.emptyList());
for (int i = 0; i < list.size(); i++) {
String key = prefix.isEmpty() ? String.valueOf(i) : prefix + "." + i;
flattenObject(list.get(i), key, out);
}
return;
}
if (value instanceof Map<?, ?> map) {
if (map.isEmpty() && !prefix.isEmpty()) out.put(prefix, Collections.emptyMap());
for (Map.Entry<?, ?> e : map.entrySet()) {
String key = String.valueOf(e.getKey());
String next = prefix.isEmpty() ? key : prefix + "." + key;
flattenObject(e.getValue(), next, out);
}
return;
}
if (!prefix.isEmpty()) out.put(prefix, value);
}
private static void assignPath(Map<String, Object> root, List<String> parts, Object value) {
Object current = root;
for (int i = 0; i < parts.size(); i++) {
String part = parts.get(i);
boolean isLast = i == parts.size() - 1;
boolean isIndex = part.matches("^\\d+$");
if (isLast) {
if (current instanceof List<?> list && isIndex) {
@SuppressWarnings("unchecked")
List<Object> target = (List<Object>) list;
int idx = Integer.parseInt(part);
while (target.size() <= idx) target.add(null);
target.set(idx, value);
} else if (current instanceof Map<?, ?> map) {
@SuppressWarnings("unchecked")
Map<String, Object> target = (Map<String, Object>) map;
target.put(part, value);
}
return;
}
boolean nextIsIndex = parts.get(i + 1).matches("^\\d+$");
if (current instanceof List<?> list && isIndex) {
@SuppressWarnings("unchecked")
List<Object> target = (List<Object>) list;
int idx = Integer.parseInt(part);
while (target.size() <= idx) target.add(null);
if (target.get(idx) == null) target.set(idx, nextIsIndex ? new ArrayList<>() : new LinkedHashMap<>());
current = target.get(idx);
} else if (current instanceof Map<?, ?> map) {
@SuppressWarnings("unchecked")
Map<String, Object> target = (Map<String, Object>) map;
if (!target.containsKey(part) || target.get(part) == null) {
target.put(part, nextIsIndex ? new ArrayList<>() : new LinkedHashMap<>());
}
current = target.get(part);
}
}
}
private static Map<String, Object> unflattenRow(Map<String, Object> flat) {
Map<String, Object> out = new LinkedHashMap<>();
for (Map.Entry<String, Object> e : flat.entrySet()) {
String key = e.getKey();
if (!key.contains(".")) {
out.put(key, e.getValue());
} else {
assignPath(out, Arrays.asList(key.split("\\.")), e.getValue());
}
}
return out;
}
@SuppressWarnings("unchecked")
private static List<Map<String, Object>> normalizeRows(Object data) {
if (data instanceof List<?> list) {
List<Map<String, Object>> out = new ArrayList<>();
for (Object item : list) {
if (item instanceof Map<?, ?> map) {
Map<String, Object> row = new LinkedHashMap<>();
for (Map.Entry<?, ?> e : map.entrySet()) row.put(String.valueOf(e.getKey()), e.getValue());
out.add(row);
} else {
Map<String, Object> row = new LinkedHashMap<>();
row.put("value", item);
out.add(row);
}
}
return out;
}
if (data instanceof Map<?, ?> map) {
Map<String, Object> row = new LinkedHashMap<>();
for (Map.Entry<?, ?> e : map.entrySet()) row.put(String.valueOf(e.getKey()), e.getValue());
return new ArrayList<>(List.of(row));
}
Map<String, Object> row = new LinkedHashMap<>();
row.put("value", data);
return new ArrayList<>(List.of(row));
}
private static List<Map<String, Object>> applyFields(List<Map<String, Object>> rows, List<String> fields) {
if (fields.isEmpty()) return rows;
List<Map<String, Object>> out = new ArrayList<>();
for (Map<String, Object> row : rows) {
Map<String, Object> flat = new LinkedHashMap<>();
flattenObject(row, "", flat);
Map<String, Object> selected = new LinkedHashMap<>();
for (String field : fields) if (flat.containsKey(field)) selected.put(field, flat.get(field));
out.add(unflattenRow(selected));
}
return out;
}
private static List<Map<String, Object>> parseInput(Path inputPath, String inputFormat, char delimiter, boolean stream, List<String> warnings) throws IOException {
if (stream && inputFormat.equals("csv")) return parseCsvStream(inputPath, delimiter);
if (stream && inputFormat.equals("json")) {
try {
return parseJsonStream(inputPath);
} catch (Exception e) {
warnings.add("JSON stream mode expects NDJSON; falling back to full parse.");
}
}
String text = decodeWithFallback(Files.readAllBytes(inputPath));
if (inputFormat.equals("csv")) return parseCsvText(text, delimiter);
if (inputFormat.equals("json")) return normalizeRows(JSON.readValue(text, Object.class));
if (inputFormat.equals("yaml")) return normalizeRows(YAML.readValue(text, Object.class));
if (inputFormat.equals("xml")) {
Object parsed = XML.readValue(text, Object.class);
if (parsed instanceof Map<?, ?> map && map.containsKey("root")) {
Object root = map.get("root");
if (root instanceof Map<?, ?> rootMap && rootMap.containsKey("item")) return normalizeRows(rootMap.get("item"));
}
return normalizeRows(parsed);
}
throw new IllegalArgumentException("Unsupported input format: " + inputFormat);
}
private static List<Map<String, Object>> parseCsvText(String text, char delimiter) {
List<String> lines = text.replace("\r\n", "\n").replace("\r", "\n").lines()
.filter(l -> !l.isBlank()).toList();
if (lines.isEmpty()) return new ArrayList<>();
List<String> headers = parseCsvLine(lines.get(0), delimiter);
List<Map<String, Object>> rows = new ArrayList<>();
for (int i = 1; i < lines.size(); i++) {
List<String> values = parseCsvLine(lines.get(i), delimiter);
Map<String, Object> flat = new LinkedHashMap<>();
for (int j = 0; j < headers.size(); j++) {
flat.put(headers.get(j).trim(), parsePrimitive(j < values.size() ? values.get(j) : ""));
}
rows.add(unflattenRow(flat));
}
return rows;
}
private static List<Map<String, Object>> parseCsvStream(Path path, char delimiter) throws IOException {
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
List<String> headers = null;
List<Map<String, Object>> rows = new ArrayList<>();
while ((line = br.readLine()) != null) {
line = stripBom(line);
if (line.isBlank()) continue;
if (headers == null) {
headers = parseCsvLine(line, delimiter);
continue;
}
List<String> values = parseCsvLine(line, delimiter);
Map<String, Object> flat = new LinkedHashMap<>();
for (int j = 0; j < headers.size(); j++) {
flat.put(headers.get(j).trim(), parsePrimitive(j < values.size() ? values.get(j) : ""));
}
rows.add(unflattenRow(flat));
}
return rows;
}
}
private static List<Map<String, Object>> parseJsonStream(Path path) throws IOException {
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
List<Map<String, Object>> rows = new ArrayList<>();
while ((line = br.readLine()) != null) {
String t = line.trim();
if (t.isBlank()) continue;
if (!(t.startsWith("{") && t.endsWith("}"))) {
throw new IllegalArgumentException("JSON stream mode expects NDJSON object lines.");
}
Map<String, Object> row = JSON.readValue(t, new TypeReference<>() {});
rows.add(row);
}
return rows;
}
}
private static String serializeCsv(List<Map<String, Object>> rows, char delimiter) {
List<Map<String, Object>> flatRows = new ArrayList<>();
LinkedHashSet<String> columns = new LinkedHashSet<>();
for (Map<String, Object> row : rows) {
Map<String, Object> flat = new LinkedHashMap<>();
flattenObject(row, "", flat);
flatRows.add(flat);
columns.addAll(flat.keySet());
}
StringBuilder out = new StringBuilder();
List<String> headers = new ArrayList<>(columns);
out.append(String.join(String.valueOf(delimiter), headers)).append("\n");
for (Map<String, Object> row : flatRows) {
List<String> vals = new ArrayList<>();
for (String h : headers) vals.add(csvEscape(row.get(h), delimiter));
out.append(String.join(String.valueOf(delimiter), vals)).append("\n");
}
return out.toString();
}
private static String serializeJson(List<Map<String, Object>> rows) throws IOException {
return JSON.writerWithDefaultPrettyPrinter().writeValueAsString(rows) + "\n";
}
private static String serializeYaml(List<Map<String, Object>> rows) throws IOException {
return YAML.writeValueAsString(rows);
}
private static String serializeXml(List<Map<String, Object>> rows) throws IOException {
Map<String, Object> root = new LinkedHashMap<>();
root.put("item", rows);
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("root", root);
return XML.writerWithDefaultPrettyPrinter().writeValueAsString(payload);
}
private static void writeOutput(Path outputPath, String outputFormat, List<Map<String, Object>> rows, char delimiter) throws IOException {
String content;
switch (outputFormat) {
case "csv" -> content = serializeCsv(rows, delimiter);
case "json" -> content = serializeJson(rows);
case "yaml" -> content = serializeYaml(rows);
case "xml" -> content = serializeXml(rows);
default -> throw new IllegalArgumentException("Unsupported output format: " + outputFormat);
}
Files.writeString(outputPath, content, StandardCharsets.UTF_8);
}
private static int countColumns(List<Map<String, Object>> rows) {
Set<String> cols = new LinkedHashSet<>();
for (Map<String, Object> row : rows) {
Map<String, Object> flat = new LinkedHashMap<>();
flattenObject(row, "", flat);
cols.addAll(flat.keySet());
}
return cols.size();
}
private static void printSummary(String inputFormat, String outputFormat, List<Map<String, Object>> rows, List<String> warnings) {
System.out.println("Conversion Summary");
System.out.println("==================");
System.out.println("Input format : " + inputFormat);
System.out.println("Output format: " + outputFormat);
System.out.println("Row count : " + rows.size());
System.out.println("Column count : " + countColumns(rows));
System.out.println("Warnings : " + (warnings.isEmpty() ? "none" : String.join(" | ", warnings)));
}
private static List<Map<String, Object>> sampleData() {
List<Map<String, Object>> rows = new ArrayList<>();
Map<String, Object> row1 = new LinkedHashMap<>();
row1.put("id", 1);
row1.put("name", "Alice");
row1.put("active", true);
row1.put("score", 97.5);
row1.put("address", new LinkedHashMap<>(Map.of("city", "Austin", "zip", "73301")));
row1.put("tags", List.of("premium", "beta"));
row1.put("orders", List.of(
new LinkedHashMap<>(Map.of("id", "o1", "amount", 39.95)),
new LinkedHashMap<>(Map.of("id", "o2", "amount", 12))
));
row1.put("last_login", null);
rows.add(row1);
Map<String, Object> row2 = new LinkedHashMap<>();
row2.put("id", 2);
row2.put("name", "Bob");
row2.put("active", false);
row2.put("score", 88);
row2.put("address", new LinkedHashMap<>(Map.of("city", "Berlin", "zip", "10115")));
row2.put("tags", List.of("standard"));
row2.put("orders", List.of(new LinkedHashMap<>(Map.of("id", "o3", "amount", 120.1))));
row2.put("last_login", "2026-01-01T10:00:00Z");
rows.add(row2);
return rows;
}
private static void runSampleMode(Map<String, String> options) throws IOException {
List<Map<String, Object>> sample = sampleData();
Path samplePath = Paths.get("sample_data.json").toAbsolutePath();
Files.writeString(samplePath, serializeJson(sample), StandardCharsets.UTF_8);
System.out.println("No input provided. Generated sample dataset: " + samplePath);
char delimiter = normalizeDelimiter(options.getOrDefault("delimiter", "comma")).charAt(0);
for (String fmt : List.of("csv", "xml", "yaml")) {
Path output = Paths.get("output." + fmt).toAbsolutePath();
writeOutput(output, fmt, sample, delimiter);
printSummary("json", fmt, sample, new ArrayList<>());
System.out.println("Output file : " + output);
System.out.println();
}
}
private static void convertSingle(Path inputPath, String outputFormat, Map<String, String> options) throws IOException {
char delimiter = normalizeDelimiter(options.getOrDefault("delimiter", "comma")).charAt(0);
List<String> warnings = new ArrayList<>();
byte[] bytes = Files.readAllBytes(inputPath);
String text = decodeWithFallback(bytes);
String encoding = detectEncoding(bytes);
if (!encoding.equals("utf-8")) warnings.add("Input decoded as " + encoding);
String inputFormat = detectInputFormat(inputPath, text, options.containsKey("detect"));
if (!FORMATS.contains(inputFormat)) throw new IllegalArgumentException("Unsupported input format: " + inputFormat);
List<Map<String, Object>> rows = parseInput(inputPath, inputFormat, delimiter, options.containsKey("stream"), warnings);
List<String> fields = new ArrayList<>();
if (options.containsKey("fields")) {
for (String f : options.get("fields").split(",")) {
String t = f.trim();
if (!t.isEmpty()) fields.add(t);
}
}
rows = applyFields(rows, fields);
if (outputFormat.equals("csv")) warnings.add("Nested objects/arrays are flattened to dot-notation columns.");
Path outputPath = options.containsKey("output")
? Paths.get(options.get("output")).toAbsolutePath()
: Paths.get("output." + outputFormat).toAbsolutePath();
writeOutput(outputPath, outputFormat, rows, delimiter);
printSummary(inputFormat, outputFormat, rows, warnings);
System.out.println("Output file : " + outputPath);
}
}