This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new b5fece3386 CSV/YAML support
b5fece3386 is described below

commit b5fece338643acc9035c68ac51a249dc76817a0c
Author: James Bognar <[email protected]>
AuthorDate: Sun Mar 1 07:32:07 2026 -0500

    CSV/YAML support
---
 .../main/java/org/apache/juneau/csv/CsvParser.java |  33 ++-
 .../org/apache/juneau/csv/CsvParserSession.java    | 210 ++++++++++++++-
 .../main/java/org/apache/juneau/csv/CsvReader.java | 222 ++++++++++++++++
 .../java/org/apache/juneau/csv/CsvSerializer.java  |  27 +-
 .../apache/juneau/csv/CsvSerializerSession.java    |  64 +++--
 .../main/java/org/apache/juneau/csv/CsvWriter.java |  30 ++-
 .../java/org/apache/juneau/yaml/YamlParser.java    |  31 +++
 .../org/apache/juneau/yaml/YamlSerializer.java     |  16 ++
 .../test/java/org/apache/juneau/ComboInput.java    |  18 +-
 .../org/apache/juneau/ComboRoundTripTest_Base.java |  22 ++
 .../org/apache/juneau/ComboRoundTrip_Tester.java   |   6 +-
 .../org/apache/juneau/ComboSerializeTest_Base.java |  40 +++
 .../org/apache/juneau/ComboSerialize_Tester.java   |  12 +-
 .../a/rttests/RoundTripAddClassAttrs_Test.java     |  11 +
 .../juneau/a/rttests/RoundTripBeanMaps_Test.java   |  11 +
 .../a/rttests/RoundTripBeansWithBuilders_Test.java |  11 +
 .../juneau/a/rttests/RoundTripDateTime_Test.java   |   6 +
 .../a/rttests/RoundTripLargeObjects_Test.java      |  11 +
 .../juneau/a/rttests/RoundTripMaps_Test.java       |  12 +
 .../juneau/a/rttests/RoundTripTest_Base.java       |  85 ++++++
 .../a/rttests/RoundTripTransformBeans_Test.java    |  11 +
 .../apache/juneau/a/rttests/RoundTrip_Tester.java  |  42 ++-
 .../java/org/apache/juneau/csv/CsvParser_Test.java | 292 +++++++++++++++++++++
 .../test/java/org/apache/juneau/csv/Csv_Test.java  |  80 ++++++
 .../org/apache/juneau/marshaller/Csv_Test.java     |  31 ++-
 25 files changed, 1277 insertions(+), 57 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParser.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParser.java
index c04bc47081..6d84768d0b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParser.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParser.java
@@ -30,7 +30,38 @@ import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.parser.*;
 
 /**
- * TODO - Work in progress.  CSV parser.
+ * Parses CSV (Comma Separated Values) input into Java objects.
+ *
+ * <p>
+ * Parses RFC 4180-compliant CSV into collections of beans, maps, or simple 
values. Each row becomes an
+ * element; the header row defines column names.
+ *
+ * <h5 class='section'>Data Structures Incompatible with CSV (vs. JSON):</h5>
+ * <p>
+ * CSV is a flat, tabular format. Unlike {@link 
org.apache.juneau.json.JsonParser JSON}, which
+ * parses nested structures and supports type discriminators, CSV cannot 
faithfully parse:
+ * <ul>
+ *   <li><b>Raw primitive arrays</b> ({@code byte[]}, {@code int[]}, etc.) — 
Cells are strings; no
+ *       unambiguous encoding. <i>JSON</i> parses arrays and base64-encoded 
bytes.
+ *   <li><b>Nested beans</b> — Cannot reconstruct bean- or map-valued 
properties from flat columns.
+ *       <i>JSON</i> parses nested objects directly.
+ *   <li><b>Collections/arrays within beans</b> — Cannot parse {@code 
List&lt;X&gt;},
+ *       {@code Map&lt;K,V&gt;}, or array properties from a single cell. 
<i>JSON</i> parses arrays
+ *       and nested objects.
+ *   <li><b>Generic type parameters</b> — No type discriminator. <i>JSON</i> 
uses {@code @type} when
+ *       configured with {@code addBeanTypes().addRootType()}.
+ *   <li><b>Parent/inherited properties</b> — Bean hierarchy cannot be 
reconstructed.
+ *       <i>JSON</i> reconstructs flattened properties into the correct bean 
hierarchy.
+ *   <li><b>Optional wrappers</b> — Round-trip may differ from tree formats. 
<i>JSON</i> handles
+ *       Optional consistently.
+ *   <li><b>Interface/abstract types</b> — No discriminator to select 
implementation. <i>JSON</i>
+ *       uses {@code @type} with {@code implClass} mappings.
+ * </ul>
+ *
+ * <h5 class='section'>Best Supported:</h5>
+ * <p>
+ * Parsing into {@link java.util.Collection} of flat beans or maps, or single 
beans/maps with simple
+ * property types (primitives, strings, numbers, enums, dates).
  *
  * <h5 class='section'>Notes:</h5><ul>
  *     <li class='note'>This class is thread safe and reusable.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParserSession.java
index 22d19171bb..794a4296ad 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvParserSession.java
@@ -16,24 +16,47 @@
  */
 package org.apache.juneau.csv;
 
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.charset.*;
 import java.util.*;
 import java.util.function.*;
+import java.util.Optional;
 
 import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.parser.*;
+import org.apache.juneau.swap.*;
 
 /**
  * Session object that lives for the duration of a single use of {@link 
CsvParser}.
  *
+ * <p>
+ * Parses CSV (Comma Separated Values) input into Java objects.  The first row 
of the CSV
+ * is treated as a header row providing column names.  Subsequent rows are 
treated as data rows.
+ *
+ * <p>
+ * The following target type mappings are supported:
+ * <ul>
+ *   <li>{@code Collection<Bean>} / {@code Bean[]} — Header row provides 
property names; each data row becomes a bean.
+ *   <li>{@code Collection<Map>} / {@code Map[]} — Header row provides map 
keys; each data row becomes a map.
+ *   <li>{@code Collection<SimpleType>} / {@code SimpleType[]} — Single {@code 
value} column; each row's value is coerced to the element type.
+ *   <li>Single {@code Bean} — Header row + one data row → one bean.
+ *   <li>Single {@code Map} — Header row + one data row → one map.
+ *   <li>{@code Object} — Returns a {@link JsonList} of {@link JsonMap} 
entries.
+ * </ul>
+ *
  * <h5 class='section'>Notes:</h5><ul>
  *     <li class='warn'>This class is not thread safe and is typically 
discarded after one use.
  * </ul>
- *
  */
+@SuppressWarnings({
+       "unchecked",
+       "rawtypes",
+})
 public class CsvParserSession extends ReaderParserSession {
 
        /**
@@ -171,19 +194,186 @@ public class CsvParserSession extends 
ReaderParserSession {
                super(builder);
        }
 
-       @SuppressWarnings({
-               "java:S1172" // Future code - parameters reserved for future 
implementation
-       })
-       private static <T> T parseAnything(ClassMeta<T> eType, ParserReader r, 
Object outer, BeanPropertyMeta pMeta) throws ParseException {
-               throw new ParseException("Not implemented.");
-       }
-
        @Override /* Overridden from ParserSession */
        protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws 
IOException, ParseException {
-               try (var r = pipe.getParserReader()) {
+               try (var r = CsvReader.from(pipe, ',', '"', isTrimStrings())) {
                        if (r == null)
                                return null;
                        return parseAnything(type, r, getOuter(), null);
                }
        }
-}
\ No newline at end of file
+
+       /**
+        * Core parse dispatch.
+        *
+        * <p>
+        * Reads the header row, then dispatches to the appropriate parsing 
strategy based on the
+        * target type.
+        */
+       private <T> T parseAnything(ClassMeta<T> eType, CsvReader r, Object 
outer, BeanPropertyMeta pMeta) throws IOException, ParseException {
+               if (eType == null)
+                       eType = (ClassMeta<T>) object();
+
+               var swap = (ObjectSwap<T,Object>) eType.getSwap(this);
+               var builder = (BuilderSwap<T,Object>) 
eType.getBuilderSwap(this);
+               ClassMeta<?> sType;
+               if (builder != null)
+                       sType = builder.getBuilderClassMeta(this);
+               else if (swap != null)
+                       sType = swap.getSwapClassMeta(this);
+               else
+                       sType = eType;
+
+               if (sType.isOptional())
+                       return (T) 
Optional.ofNullable(parseAnything(eType.getElementType(), r, outer, pMeta));
+
+               // Read header row
+               var headers = r.readRow();
+               if (headers == null || headers.isEmpty())
+                       return null;
+
+               Object o = null;
+
+               if (sType.isArray()) {
+                       var elementType = sType.getElementType();
+                       var list = list();
+                       for (var row = r.readRow(); row != null; row = 
r.readRow())
+                               list.add(parseRow(headers, row, elementType, 
list));
+                       o = toArray(sType, list);
+
+               } else if (sType.isCollection()) {
+                       var elementType = sType.getElementType();
+                       Collection<Object> l = 
sType.canCreateNewInstance(outer) ? (Collection<Object>) sType.newInstance() : 
new ArrayList<>();
+                       for (var row = r.readRow(); row != null; row = 
r.readRow())
+                               l.add(parseRow(headers, row, elementType, l));
+                       o = l;
+
+               } else if (sType.isBean()) {
+                       var row = r.readRow();
+                       if (row != null)
+                               o = parseRowIntoBean(headers, row, sType, 
outer);
+
+               } else if (sType.isMap()) {
+                       var row = r.readRow();
+                       if (row != null)
+                               o = parseRowIntoMap(headers, row, sType, outer);
+
+               } else if (sType.isObject()) {
+                       // For Object target type: return a JsonList of 
JsonMaps (or a single JsonMap if one row)
+                       var results = new JsonList(this);
+                       for (var row = r.readRow(); row != null; row = 
r.readRow()) {
+                               var m = new JsonMap(this);
+                               for (var i = 0; i < headers.size(); i++) {
+                                       var val = i < row.size() ? row.get(i) : 
null;
+                                       m.put(headers.get(i), 
parseCellValue(val, object()));
+                               }
+                               results.add(m);
+                       }
+                       o = results.isEmpty() ? null : (results.size() == 1 ? 
results.get(0) : results);
+               } else {
+                       // For simple target types (String, Number, Boolean, 
etc.) that are not beans/maps/collections,
+                       // treat CSV as a single "value" column.  Read the 
first data row's value column.
+                       var valueColIdx = headers.indexOf("value");
+                       if (valueColIdx < 0) valueColIdx = 0;
+                       var row = r.readRow();
+                       if (row != null && valueColIdx < row.size())
+                               o = parseCellValue(row.get(valueColIdx), sType);
+               }
+
+               if (builder != null && o != null)
+                       o = builder.build(this, o, eType);
+
+               if (swap != null && o != null)
+                       o = unswap(swap, o, eType);
+
+               return (T) o;
+       }
+
+       /**
+        * Parses a single data row into the appropriate element object.
+        */
+       private Object parseRow(List<String> headers, List<String> row, 
ClassMeta<?> eType, Object outer) throws ParseException {
+               if (eType == null || eType.isObject()) {
+                       var m = new JsonMap(this);
+                       for (var i = 0; i < headers.size(); i++) {
+                               var val = i < row.size() ? row.get(i) : null;
+                               m.put(headers.get(i), parseCellValue(val, 
object()));
+                       }
+                       return m;
+               } else if (eType.isBean()) {
+                       return parseRowIntoBean(headers, row, eType, outer);
+               } else if (eType.isMap()) {
+                       return parseRowIntoMap(headers, row, eType, outer);
+               } else {
+                       // Simple type: use the "value" column (first column) 
or the only column present
+                       var val = row.isEmpty() ? null : row.get(0);
+                       return parseCellValue(val, eType);
+               }
+       }
+
+       /**
+        * Parses a single data row into a bean of the specified type.
+        */
+       private <T> T parseRowIntoBean(List<String> headers, List<String> row, 
ClassMeta<T> eType, Object outer) throws ParseException {
+               var m = newBeanMap(outer, eType.inner());
+               for (var i = 0; i < headers.size(); i++) {
+                       var header = headers.get(i);
+                       var val = i < row.size() ? row.get(i) : null;
+                       var pm = m.getPropertyMeta(header);
+                       if (pm != null) {
+                               setCurrentProperty(pm);
+                               var converted = parseCellValue(val, 
pm.getClassMeta());
+                               pm.set(m, header, converted);
+                               setCurrentProperty(null);
+                       } else {
+                               onUnknownProperty(header, m, val);
+                       }
+               }
+               return m.getBean();
+       }
+
+       /**
+        * Parses a single data row into a map of the specified type.
+        */
+       @SuppressWarnings("java:S3740")
+       private Object parseRowIntoMap(List<String> headers, List<String> row, 
ClassMeta<?> eType, Object outer) throws ParseException {
+               Map m;
+               if (eType.canCreateNewInstance(outer))
+                       m = (Map) eType.newInstance(outer);
+               else
+                       m = new JsonMap(this);
+               var keyType = eType.getKeyType() != null ? eType.getKeyType() : 
string();
+               var valueType = eType.getValueType() != null ? 
eType.getValueType() : object();
+               for (var i = 0; i < headers.size(); i++) {
+                       var header = headers.get(i);
+                       var val = i < row.size() ? row.get(i) : null;
+                       var key = convertAttrToType(m, header, keyType);
+                       var value = parseCellValue(val, valueType);
+                       m.put(key, value);
+               }
+               return m;
+       }
+
+       /**
+        * Converts a raw CSV cell string value to the target type.
+        *
+        * <p>
+        * The unquoted literal {@code null} maps to Java {@code null}.
+        * All other values are converted via {@link #convertToType(Object, 
ClassMeta)}.
+        */
+       private <T> T parseCellValue(String val, ClassMeta<T> eType) throws 
ParseException {
+               if (val == null || val.equals("null"))
+                       return null;
+               // Apply trimStrings at the cell level (before type conversion) 
so that the String
+               // value passed to convertToType() is already trimmed.
+               if (isTrimStrings() && val != null)
+                       val = val.trim();
+               if (val.isEmpty() && eType.isCharSequence())
+                       return null;
+               try {
+                       return convertToType(val, eType);
+               } catch (InvalidDataConversionException e) {
+                       throw new ParseException(e, "Could not convert CSV cell 
value ''{0}'' to type ''{1}''.", val, eType);
+               }
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvReader.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvReader.java
new file mode 100644
index 0000000000..feb5f07780
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvReader.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.csv;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.parser.*;
+
+/**
+ * Specialized reader for parsing CSV input.
+ *
+ * <p>
+ * Parses CSV data according to RFC 4180 with support for:
+ * <ul>
+ *   <li>Quoted fields (configurable quote character)
+ *   <li>Embedded delimiter characters within quoted fields
+ *   <li>Embedded newlines within quoted fields
+ *   <li>Embedded quote characters escaped by doubling (RFC 4180 style)
+ *   <li>Both CRLF and LF line endings
+ *   <li>Optional whitespace trimming
+ * </ul>
+ *
+ * <h5 class='section'>Notes:</h5><ul>
+ *     <li class='note'>This class is not intended for external use.
+ * </ul>
+ */
+public class CsvReader implements Closeable {
+
+       private final ParserReader r;
+       private final char delimiter;
+       private final char quoteChar;
+       private final boolean trimStrings;
+       private boolean eof = false;
+
+       /**
+        * Constructor.
+        *
+        * @param r The parser reader to wrap.
+        * @param delimiter The field delimiter character (typically 
<js>','</js>).
+        * @param quoteChar The quote character (typically <js>'"'</js>).
+        * @param trimStrings If <jk>true</jk>, field values are trimmed of 
surrounding whitespace.
+        */
+       public CsvReader(ParserReader r, char delimiter, char quoteChar, 
boolean trimStrings) {
+               this.r = r;
+               this.delimiter = delimiter;
+               this.quoteChar = quoteChar;
+               this.trimStrings = trimStrings;
+       }
+
+       /**
+        * Creates a {@link CsvReader} from a {@link ParserPipe}.
+        *
+        * @param pipe The parser pipe.
+        * @param delimiter The delimiter character.
+        * @param quoteChar The quote character.
+        * @param trimStrings Whether to trim strings.
+        * @return A new {@link CsvReader}, or <jk>null</jk> if the pipe has no 
input.
+        * @throws IOException Thrown by underlying stream.
+        */
+       public static CsvReader from(ParserPipe pipe, char delimiter, char 
quoteChar, boolean trimStrings) throws IOException {
+               var pr = pipe.getParserReader();
+               if (pr == null)
+                       return null;
+               return new CsvReader(pr, delimiter, quoteChar, trimStrings);
+       }
+
+       /**
+        * Reads a single row of CSV data, returning <jk>null</jk> at end of 
input.
+        *
+        * <p>
+        * Each call advances past one record (terminated by LF, CRLF, or 
end-of-file).
+        * Quoted fields may span multiple lines.
+        *
+        * @return A list of field values for the row, or <jk>null</jk> if end 
of input has been reached.
+        * @throws IOException Thrown by underlying stream.
+        * @throws ParseException If the CSV is malformed (e.g. an unclosed 
quoted field).
+        */
+       @SuppressWarnings("java:S3776")
+       public List<String> readRow() throws IOException, ParseException {
+               if (eof)
+                       return null;
+
+               // Skip blank lines between records
+               int c = r.read();
+               while (c == '\r' || c == '\n') {
+                       if (c == '\r') {
+                               int next = r.read();
+                               if (next != '\n' && next != -1)
+                                       r.unread();
+                       }
+                       c = r.read();
+               }
+
+               if (c == -1) {
+                       eof = true;
+                       return null;
+               }
+
+               r.unread();
+
+               var fields = new ArrayList<String>();
+               var field = new StringBuilder();
+
+               while (true) {
+                       c = r.read();
+
+                       if (c == -1) {
+                               eof = true;
+                               fields.add(finalize(field));
+                               return fields;
+                       }
+
+                       if (c == quoteChar) {
+                               parseQuotedField(field);
+                               // After a quoted field, read the next char: 
delimiter, newline, or EOF
+                               int next = r.read();
+                               if (next == -1) {
+                                       eof = true;
+                                       fields.add(finalize(field));
+                                       return fields;
+                               } else if (next == delimiter) {
+                                       fields.add(finalize(field));
+                                       field = new StringBuilder();
+                               } else if (next == '\r') {
+                                       int peek = r.read();
+                                       if (peek != '\n' && peek != -1)
+                                               r.unread();
+                                       fields.add(finalize(field));
+                                       return fields;
+                               } else if (next == '\n') {
+                                       fields.add(finalize(field));
+                                       return fields;
+                               } else {
+                                       // Lenient: treat extra content after 
closing quote as unquoted
+                                       field.append((char) next);
+                               }
+                       } else if (c == delimiter) {
+                               fields.add(finalize(field));
+                               field = new StringBuilder();
+                       } else if (c == '\r') {
+                               int next = r.read();
+                               if (next != '\n' && next != -1)
+                                       r.unread();
+                               fields.add(finalize(field));
+                               return fields;
+                       } else if (c == '\n') {
+                               fields.add(finalize(field));
+                               return fields;
+                       } else {
+                               field.append((char) c);
+                       }
+               }
+       }
+
+       /**
+        * Parses the contents of a quoted field.
+        *
+        * <p>
+        * The opening quote character has already been consumed.
+        * Handles RFC 4180 doubled-quote escaping: two consecutive quote 
characters inside a quoted field
+        * produce a single literal quote character.
+        *
+        * @param field The string builder to append field content to.
+        * @throws IOException Thrown by underlying stream.
+        * @throws ParseException If end of input is reached before the closing 
quote.
+        */
+       private void parseQuotedField(StringBuilder field) throws IOException, 
ParseException {
+               while (true) {
+                       int c = r.read();
+                       if (c == -1)
+                               throw new ParseException("Unterminated quoted 
field in CSV input.");
+                       if (c == quoteChar) {
+                               int next = r.read();
+                               if (next == quoteChar) {
+                                       // Doubled quote → literal quote
+                                       field.append((char) quoteChar);
+                               } else {
+                                       // Closing quote; push back the 
following character
+                                       if (next != -1)
+                                               r.unread();
+                                       return;
+                               }
+                       } else {
+                               field.append((char) c);
+                       }
+               }
+       }
+
+       private String finalize(StringBuilder sb) {
+               var s = sb.toString();
+               return trimStrings ? s.trim() : s;
+       }
+
+       /**
+        * Returns whether the end of input has been reached.
+        *
+        * @return <jk>true</jk> if end of input has been reached.
+        */
+       public boolean isEof() {
+               return eof;
+       }
+
+       @Override
+       public void close() throws IOException {
+               r.close();
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializer.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializer.java
index dc5c037b60..3b9ff7639c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializer.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializer.java
@@ -37,10 +37,33 @@ import org.apache.juneau.serializer.*;
  * becomes a row and bean properties become columns. The first row typically 
contains column headers derived
  * from bean property names.
  *
+ * <h5 class='section'>Data Structures Incompatible with CSV (vs. JSON):</h5>
+ * <p>
+ * CSV is a flat, tabular format. Unlike {@link 
org.apache.juneau.json.JsonSerializer JSON}, which
+ * supports nested objects, arrays, and type discriminators, CSV cannot 
faithfully represent:
+ * <ul>
+ *   <li><b>Raw primitive arrays</b> ({@code byte[]}, {@code int[]}, etc.) — 
Serialize as
+ *       {@code Object.toString()} (e.g. {@code [B@12345}). <i>JSON</i> 
supports arrays natively
+ *       and typically uses base64 for {@code byte[]}.
+ *   <li><b>Nested beans</b> — Flatten to a single row; structure is lost. 
<i>JSON</i> preserves
+ *       nested objects naturally ({@code {"a":{"b":"c"}}}).
+ *   <li><b>Collections/arrays within beans</b> — {@code List&lt;X&gt;}, 
{@code Map&lt;K,V&gt;}, and
+ *       array properties serialize as {@code toString()}; they cannot be 
parsed back. <i>JSON</i>
+ *       supports arrays and objects as first-class values.
+ *   <li><b>Generic type preservation</b> — No type discriminator. <i>JSON</i> 
can add {@code @type}
+ *       via {@code addBeanTypes().addRootType()}.
+ *   <li><b>Parent/inherited properties</b> — Hierarchy flattens; may collide 
with child names.
+ *       <i>JSON</i> serializes all properties in a single object without loss.
+ *   <li><b>Optional wrappers</b> — May serialize as the inner value; 
round-trip differs from
+ *       tree formats. <i>JSON</i> handles Optional consistently as value or 
null.
+ * </ul>
+ *
+ * <h5 class='section'>Best Supported:</h5>
+ * <p>
+ * Collections of flat beans or maps whose properties are primitives, strings, 
numbers, enums, or dates.
+ *
  * <h5 class='section'>Notes:</h5><ul>
  *     <li class='note'>This class is thread safe and reusable.
- *     <li class='warn'>This serializer is optimized for simple tabular data 
structures and may have limitations
- *             with complex nested objects.
  * </ul>
  *
  */
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
index f2e642319e..a1d3238eb6 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
@@ -253,56 +253,83 @@ public class CsvSerializerSession extends 
WriterSerializerSession {
                                l = Collections.singleton(o);
                        }
 
-                       // TODO - Doesn't support DynaBeans.
                        if (ne(l)) {
                                var firstOpt = first(l);
                                if (!firstOpt.isPresent())
                                        return;
-                               var entryType = 
getClassMetaForObject(firstOpt.get());
-                               if (entryType.isBean()) {
-                                       var bm = entryType.getBeanMeta();
-                                       var addComma = Flag.create();
 
+                               // Apply any registered swap to the first 
element to determine column structure.
+                               var firstRaw = firstOpt.get();
+                               var firstEntry = applySwap(firstRaw, 
getClassMetaForObject(firstRaw));
+                               var entryType = 
getClassMetaForObject(firstEntry);
+
+                               // Determine the best representation strategy.
+                               // Use the BeanMeta from the entry type for 
bean serialization.
+                               var bm = entryType.isBean() ? 
entryType.getBeanMeta() : null;
+
+                               if (bm != null) {
+                                       // Bean or DynaBean path: header row = 
property names
+                                       var addComma = Flag.create();
                                        
bm.getProperties().values().stream().filter(BeanPropertyMeta::canRead).forEach(x
 -> {
                                                addComma.ifSet(() -> 
w.w(',')).set();
                                                w.writeEntry(x.getName());
                                        });
                                        w.append('\n');
+                                       var readableProps = 
bm.getProperties().values().stream().filter(BeanPropertyMeta::canRead).toList();
                                        l.forEach(x -> {
                                                var addComma2 = Flag.create();
-                                               BeanMap<?> bean = toBeanMap(x);
-                                               
bm.getProperties().values().stream().filter(BeanPropertyMeta::canRead).forEach(y
 -> {
-                                               addComma2.ifSet(() -> 
w.w(',')).set();
-                                               var value = y.get(bean, 
y.getName());
-                                               value = 
formatIfDateOrDuration(value);
-                                               w.writeEntry(value);
-                                               });
+                                               if (x == null) {
+                                                       // Null entry: write 
null for each column
+                                                       readableProps.forEach(y 
-> {
+                                                               
addComma2.ifSet(() -> w.w(',')).set();
+                                                               
w.writeEntry(null);
+                                                       });
+                                               } else {
+                                                       // Apply swap before 
extracting bean properties (e.g. surrogate swaps)
+                                                       var swapped = 
applySwap(x, getClassMetaForObject(x));
+                                                       BeanMap<?> bean = 
toBeanMap(swapped);
+                                                       readableProps.forEach(y 
-> {
+                                                               
addComma2.ifSet(() -> w.w(',')).set();
+                                                               var value = 
y.get(bean, y.getName());
+                                                               value = 
formatIfDateOrDuration(value);
+                                                               // Use 
toString() to respect trimStrings setting on String values
+                                                               if (value 
instanceof String s) value = toString(s);
+                                                               
w.writeEntry(value);
+                                                       });
+                                               }
                                                w.w('\n');
                                        });
                                } else if (entryType.isMap()) {
+                                       // Map path: header row = map keys from 
the first entry
                                        var addComma = Flag.create();
-                                       var first = (Map)firstOpt.get();
+                                       var first = (Map) firstEntry;
                                        first.keySet().forEach(x -> {
                                                addComma.ifSet(() -> 
w.w(',')).set();
-                                               w.writeEntry(x);
+                                               // Apply trimStrings to map 
keys as well
+                                               w.writeEntry(x instanceof 
String s ? toString(s) : x);
                                        });
                                        w.append('\n');
-                                       l.stream().forEach(x -> {
+                                       l.forEach(x -> {
                                                var addComma2 = Flag.create();
-                                               var map = (Map)x;
+                                               var swapped = applySwap(x, 
getClassMetaForObject(x));
+                                               var map = (Map) swapped;
                                                map.values().forEach(y -> {
                                                        addComma2.ifSet(() -> 
w.w(',')).set();
                                                        var value = 
applySwap(y, getClassMetaForObject(y));
+                                                       // Apply trimStrings to 
map values
+                                                       if (value instanceof 
String s) value = toString(s);
                                                        w.writeEntry(value);
                                                });
                                                w.w('\n');
                                        });
                                } else {
+                                       // Simple value path: single "value" 
column
                                        w.writeEntry("value");
                                        w.append('\n');
-                                       l.stream().forEach(x -> {
+                                       l.forEach(x -> {
                                                var value = applySwap(x, 
getClassMetaForObject(x));
-                                               w.writeEntry(value);
+                                               // Use toString() to respect 
trimStrings setting
+                                               w.writeEntry(value == null ? 
null : toString(value));
                                                w.w('\n');
                                        });
                                }
@@ -310,6 +337,7 @@ public class CsvSerializerSession extends 
WriterSerializerSession {
                }
        }
 
+
        private Object formatIfDateOrDuration(Object value) {
                if (value == null)
                        return null;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvWriter.java
index fda18c7072..55f51a03a1 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvWriter.java
@@ -183,23 +183,39 @@ public class CsvWriter extends SerializerWriter {
        /**
         * Writes an entry to the writer.
         *
+        * <p>
+        * Follows RFC 4180 quoting rules:
+        * <ul>
+        *   <li>Values containing commas, double-quote characters, or newlines 
are enclosed in double quotes.
+        *   <li>Double-quote characters within a quoted field are escaped by 
preceding them with another double-quote.
+        *   <li>The literal string {@code null} is written unquoted; the 
string value {@code "null"} is quoted.
+        * </ul>
+        *
         * @param value The value to write.
         */
        public void writeEntry(Object value) {
-               if (value == null)
+               if (value == null) {
                        w("null");
-               else {
+               } else {
                        var s = value.toString();
                        var mustQuote = false;
-                       for (var i = 0; i < s.length() && ! mustQuote; i++) {
+                       for (var i = 0; i < s.length() && !mustQuote; i++) {
                                var c = s.charAt(i);
-                               if (Character.isWhitespace(c) || c == ',')
+                               if (c == ',' || c == '"' || c == '\r' || c == 
'\n')
                                        mustQuote = true;
                        }
-                       if (mustQuote)
-                               w('"').w(s).w('"');
-                       else
+                       if (mustQuote) {
+                               w('"');
+                               for (var i = 0; i < s.length(); i++) {
+                                       var c = s.charAt(i);
+                                       if (c == '"')
+                                               w('"');  // RFC 4180: escape 
embedded quote by doubling it
+                                       w(c);
+                               }
+                               w('"');
+                       } else {
                                w(s);
+                       }
                }
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParser.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParser.java
index 626d2552d8..89ba21c316 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParser.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParser.java
@@ -38,9 +38,40 @@ import org.apache.juneau.parser.*;
  * <p>
  * Handles <c>Content-Type</c> types:  <bc>application/yaml, text/yaml</bc>
  *
+ * <h5 class='topic'>Description</h5>
+ * <p>
+ * This parser uses an indentation-aware state machine to parse YAML directly 
into POJOs without intermediate
+ * DOM objects.  It supports block-style and flow-style mappings and 
sequences, plain and quoted scalars,
+ * comments, document markers, and standard YAML scalar types.
+ *
+ * <h5 class='section'>Limitations compared to JSON</h5>
+ * <p>
+ * The YAML parser has some limitations when compared to {@link 
org.apache.juneau.json.JsonParser JsonParser}:
+ * <ul class='spaced-list'>
+ *     <li>
+ *             Maps with non-String keys ({@link Boolean}, {@link 
java.util.Date}, {@link java.time.temporal.Temporal},
+ *             {@link Enum}) may not round-trip correctly when the target type 
is a generic {@link java.util.Map} with
+ *             those key types.  {@link java.util.LinkedHashMap LinkedHashMap} 
and {@link java.util.TreeMap TreeMap}
+ *             with {@link String} keys work reliably.
+ *     <li>
+ *             {@link java.util.HashMap HashMap} instances that contain a 
{@code null} key can fail to round-trip in
+ *             some cases; {@link java.util.LinkedHashMap LinkedHashMap} and 
{@link java.util.TreeMap TreeMap} handle
+ *             null keys correctly.
+ *     <li>
+ *             No strict vs non-strict mode; unlike JSON, there is no 
equivalent to JSON's lax parsing of comments,
+ *             unquoted attributes, or concatenated strings.
+ *     <li>
+ *             YAML's indentation-based structure requires consistent 
formatting; malformed indentation can cause
+ *             parsing errors where equivalent JSON would parse successfully.
+ * </ul>
+ *
  * <h5 class='section'>Notes:</h5><ul>
  *     <li class='note'>This class is thread safe and reusable.
  * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/YamlBasics";>YAML Basics</a>
+ * </ul>
  */
 @SuppressWarnings({
        "java:S110", // Inheritance depth acceptable
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializer.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializer.java
index b6c95ac26e..b041d1c68e 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializer.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializer.java
@@ -89,9 +89,25 @@ import org.apache.juneau.serializer.*;
  *     String <jv>yaml</jv> = 
<jv>serializer</jv>.serialize(<jv>someObject</jv>);
  * </p>
  *
+ * <h5 class='section'>Limitations compared to JSON</h5>
+ * <p>
+ * The YAML serializer has fewer configuration options than {@link 
org.apache.juneau.json.JsonSerializer JsonSerializer}:
+ * <ul class='spaced-list'>
+ *     <li>
+ *             No compact single-line output mode; YAML is always emitted in 
block-style (indentation-based) format.
+ *     <li>
+ *             No equivalent to JSON's simple mode or attribute quoting style 
variants (single vs double quotes).
+ *     <li>
+ *             No strict vs lax output modes; YAML output follows a 
consistent, human-readable style.
+ * </ul>
+ *
  * <h5 class='section'>Notes:</h5><ul>
  *     <li class='note'>This class is thread safe and reusable.
  * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/YamlBasics";>YAML Basics</a>
+ * </ul>
  */
 @SuppressWarnings({
        "java:S110", // Inheritance depth acceptable for this class hierarchy
diff --git a/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java 
b/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java
index 4048978985..017ea5ad4c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java
@@ -40,7 +40,7 @@ public class ComboInput<T> {
        List<Class<?>> swaps = list();
        final Type type;
        String json, jsonT, jsonR, xml, xmlT, xmlR, xmlNs, html, htmlT, htmlR, 
uon, uonT, uonR, urlEncoding,
-               urlEncodingT, urlEncodingR, msgPack, msgPackT, rdfXml, rdfXmlT, 
rdfXmlR;
+               urlEncodingT, urlEncodingR, msgPack, msgPackT, rdfXml, rdfXmlT, 
rdfXmlR, csv, yaml, yamlT, yamlR;
        List<Tuple2<Class<?>,Consumer<?>>> applies = list();
 
        public ComboInput<T> beanContext(Consumer<BeanContext.Builder> c) {
@@ -179,6 +179,22 @@ public class ComboInput<T> {
                rdfXmlR = value;
                return this;
        }
+       public ComboInput<T> csv(String value) {
+               csv = value;
+               return this;
+       }
+       public ComboInput<T> yaml(String value) {
+               yaml = value;
+               return this;
+       }
+       public ComboInput<T> yamlT(String value) {
+               yamlT = value;
+               return this;
+       }
+       public ComboInput<T> yamlR(String value) {
+               yamlR = value;
+               return this;
+       }
 
        public ComboInput(
                        String label,
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTripTest_Base.java 
b/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTripTest_Base.java
index 385049bb9e..e30fe12e0d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTripTest_Base.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTripTest_Base.java
@@ -506,4 +506,26 @@ public abstract class ComboRoundTripTest_Base extends 
TestBase {
        public void g33_verifyYamlR(ComboRoundTrip_Tester<?> t) throws 
Exception {
                t.testParseVerify("yamlR");
        }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // CSV
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void h11_serializeCsv(ComboRoundTrip_Tester<?> t) throws 
Exception {
+               t.testSerialize("csv");
+       }
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void h12_parseCsv(ComboRoundTrip_Tester<?> t) throws Exception {
+               t.testParse("csv");
+       }
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void h13_verifyCsv(ComboRoundTrip_Tester<?> t) throws Exception {
+               t.testParseVerify("csv");
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTrip_Tester.java 
b/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTrip_Tester.java
index b51ab17fba..fdeca0be2b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTrip_Tester.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ComboRoundTrip_Tester.java
@@ -26,6 +26,7 @@ import java.util.*;
 import java.util.function.*;
 
 import org.apache.juneau.commons.function.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
@@ -119,6 +120,7 @@ public class ComboRoundTrip_Tester<T> {
                public Builder<T> yaml(String value) { expected.put("yaml", 
value); return this; }
                public Builder<T> yamlT(String value) { expected.put("yamlT", 
value); return this; }
                public Builder<T> yamlR(String value) { expected.put("yamlR", 
value); return this; }
+               public Builder<T> csv(String value) { expected.put("csv", 
value); return this; }
 
                public ComboRoundTrip_Tester<T> build() {
                        return new ComboRoundTrip_Tester<>(this);
@@ -167,6 +169,7 @@ public class ComboRoundTrip_Tester<T> {
                serializers.put("yaml", create(b, 
YamlSerializer.DEFAULT.copy().addBeanTypes().addRootType()));
                serializers.put("yamlT", create(b, 
YamlSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
                serializers.put("yamlR", create(b, 
YamlSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
+               serializers.put("csv", create(b, CsvSerializer.create()));
 
                parsers.put("json", create(b, JsonParser.DEFAULT.copy()));
                parsers.put("jsonT", create(b, 
JsonParser.create().typePropertyName("t")));
@@ -189,6 +192,7 @@ public class ComboRoundTrip_Tester<T> {
                parsers.put("yaml", create(b, YamlParser.DEFAULT.copy()));
                parsers.put("yamlT", create(b, 
YamlParser.create().typePropertyName("t")));
                parsers.put("yamlR", create(b, YamlParser.DEFAULT.copy()));
+               parsers.put("csv", create(b, CsvParser.create()));
        }
 
        private Serializer create(Builder<?> tb, Serializer.Builder sb) {
@@ -282,7 +286,7 @@ public class ComboRoundTrip_Tester<T> {
                var s = serializers.get(testName);
                var p = parsers.get(testName);
                try {
-                       if (isSkipped(testName + "verify", "")) return;
+                       if (isSkipped(testName + "verify", 
expected.get(testName))) return;
 
                        var r = s.serializeToString(in.get());
                        var o = p.parse(r, type);
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ComboSerializeTest_Base.java 
b/juneau-utest/src/test/java/org/apache/juneau/ComboSerializeTest_Base.java
index 9601c8b9f4..b3a876859f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ComboSerializeTest_Base.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ComboSerializeTest_Base.java
@@ -203,4 +203,44 @@ public abstract class ComboSerializeTest_Base extends 
TestBase {
        public void f21_serializeMsgPackT(ComboSerialize_Tester<?> t) throws 
Exception {
                t.testSerialize("msgPackT");
        }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // YAML
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void g11_serializeYaml(ComboSerialize_Tester<?> t) throws 
Exception {
+               t.testSerialize("yaml");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // YAML - 't' property
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void g21_serializeYamlT(ComboSerialize_Tester<?> t) throws 
Exception {
+               t.testSerialize("yamlT");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // YAML - Readable
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void g31_serializeYamlR(ComboSerialize_Tester<?> t) throws 
Exception {
+               t.testSerialize("yamlR");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // CSV
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       public void h11_serializeCsv(ComboSerialize_Tester<?> t) throws 
Exception {
+               t.testSerialize("csv");
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ComboSerialize_Tester.java 
b/juneau-utest/src/test/java/org/apache/juneau/ComboSerialize_Tester.java
index 3b97425ca7..2011ceedc1 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ComboSerialize_Tester.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ComboSerialize_Tester.java
@@ -26,6 +26,7 @@ import java.util.*;
 import java.util.function.*;
 
 import org.apache.juneau.commons.function.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
@@ -33,6 +34,7 @@ import org.apache.juneau.serializer.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 
 /**
  * Represents the input to a ComboTest.
@@ -100,6 +102,10 @@ public class ComboSerialize_Tester<T> {
                public Builder<T> rdfXml(String value) { expected.put("rdfXml", 
value); return this; }
                public Builder<T> rdfXmlT(String value) { 
expected.put("rdfXmlT", value); return this; }
                public Builder<T> rdfXmlR(String value) { 
expected.put("rdfXmlR", value); return this; }
+               public Builder<T> csv(String value) { expected.put("csv", 
value); return this; }
+               public Builder<T> yaml(String value) { expected.put("yaml", 
value); return this; }
+               public Builder<T> yamlT(String value) { expected.put("yamlT", 
value); return this; }
+               public Builder<T> yamlR(String value) { expected.put("yamlR", 
value); return this; }
 
                public ComboSerialize_Tester<T> build() {
                        return new ComboSerialize_Tester<>(this);
@@ -138,6 +144,10 @@ public class ComboSerialize_Tester<T> {
                serializers.put("urlEncR", create(b, 
UrlEncodingSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
                serializers.put("msgPack", create(b, 
MsgPackSerializer.create().addBeanTypes().addRootType()));
                serializers.put("msgPackT", create(b, 
MsgPackSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
+               serializers.put("csv", create(b, CsvSerializer.create()));
+               serializers.put("yaml", create(b, 
YamlSerializer.DEFAULT.copy().addBeanTypes().addRootType()));
+               serializers.put("yamlT", create(b, 
YamlSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
+               serializers.put("yamlR", create(b, 
YamlSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
        }
 
        private Serializer create(Builder<?> tb, Serializer.Builder sb) {
@@ -153,7 +163,7 @@ public class ComboSerialize_Tester<T> {
        }
 
        private boolean isSkipped(String testName, String expected) {
-               return "SKIP".equals(expected) || skipTest.test(testName);
+               return expected == null || "SKIP".equals(expected) || 
skipTest.test(testName);
        }
 
        public void testSerialize(String testName) throws Exception {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripAddClassAttrs_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripAddClassAttrs_Test.java
index 681eb6f2b2..fecb344d45 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripAddClassAttrs_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripAddClassAttrs_Test.java
@@ -24,12 +24,14 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
@@ -78,6 +80,15 @@ class RoundTripAddClassAttrs_Test extends TestBase {
                tester(9, "MsgPackSerializer.DEFAULT/MsgPackParser.DEFAULT")
                        
.serializer(MsgPackSerializer.create().addBeanTypes().addRootType())
                        
.parser(MsgPackParser.create().disableInterfaceProxies())
+                       .build(),
+               tester(10, "Yaml - default")
+                       
.serializer(YamlSerializer.create().addBeanTypes().addRootType())
+                       .parser(YamlParser.create().disableInterfaceProxies())
+                       .build(),
+               tester(11, "Csv - default")
+                       .serializer(CsvSerializer.create())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
                        .build()
        };
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMaps_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMaps_Test.java
index 91f768d187..c2bf2f539e 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMaps_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMaps_Test.java
@@ -29,6 +29,7 @@ import javax.xml.datatype.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.bean.html5.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.json.annotation.*;
@@ -36,6 +37,7 @@ import org.apache.juneau.msgpack.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
@@ -117,6 +119,15 @@ class RoundTripBeanMaps_Test extends TestBase {
                        
.serializer(JsonSchemaSerializer.create().keepNullProperties().addBeanTypes().addRootType())
                        .returnOriginalObject()
                        .build(),
+               tester(17, "Yaml - default")
+                       
.serializer(YamlSerializer.create().keepNullProperties().addBeanTypes().addRootType())
+                       .parser(YamlParser.create())
+                       .build(),
+               tester(18, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
+                       .build(),
        };
 
        static RoundTrip_Tester[]  testers() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeansWithBuilders_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeansWithBuilders_Test.java
index d0450579a4..65f868abe1 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeansWithBuilders_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeansWithBuilders_Test.java
@@ -25,12 +25,14 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
@@ -111,6 +113,15 @@ class RoundTripBeansWithBuilders_Test extends TestBase {
                        
.serializer(JsonSchemaSerializer.create().keepNullProperties().addBeanTypes().addRootType())
                        .returnOriginalObject()
                        .build(),
+               tester(17, "Yaml - default")
+                       
.serializer(YamlSerializer.create().keepNullProperties().addBeanTypes().addRootType())
+                       .parser(YamlParser.create())
+                       .build(),
+               tester(18, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
+                       .build(),
        };
 
        static RoundTrip_Tester[] testers() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripDateTime_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripDateTime_Test.java
index 45ed8cce98..0e7ba1e580 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripDateTime_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripDateTime_Test.java
@@ -24,6 +24,7 @@ import java.util.*;
 import javax.xml.datatype.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
@@ -114,6 +115,11 @@ class RoundTripDateTime_Test extends TestBase {
                        
.serializer(YamlSerializer.create().keepNullProperties().addBeanTypes().addRootType())
                        .parser(YamlParser.create())
                        .build(),
+               tester(18, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
+                       .build(),
        };
 
        static RoundTrip_Tester[] testers() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
index 6cc6990ea5..e84e789025 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
@@ -22,12 +22,14 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
@@ -83,6 +85,15 @@ class RoundTripLargeObjects_Test extends TestBase {
                tester(9, "MsgPack")
                        
.serializer(MsgPackSerializer.create().keepNullProperties())
                        .parser(MsgPackParser.create())
+                       .build(),
+               tester(10, "Yaml - default")
+                       
.serializer(YamlSerializer.create().keepNullProperties())
+                       .parser(YamlParser.create())
+                       .build(),
+               tester(11, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
                        .build()
        };
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripMaps_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripMaps_Test.java
index b7245c78ca..4554ac084a 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripMaps_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripMaps_Test.java
@@ -23,6 +23,7 @@ import java.time.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
@@ -31,6 +32,7 @@ import org.apache.juneau.swaps.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
@@ -115,6 +117,16 @@ class RoundTripMaps_Test extends TestBase {
                        
.serializer(JsonSchemaSerializer.create().keepNullProperties().addBeanTypes().addRootType())
                        .returnOriginalObject()
                        .build(),
+               tester(17, "Yaml - default")
+                       
.serializer(YamlSerializer.create().keepNullProperties().addBeanTypes().addRootType())
+                       .parser(YamlParser.create())
+                       .skipIf(o -> o instanceof java.util.HashMap)
+                       .build(),
+               tester(18, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
+                       .build(),
        };
 
        static RoundTrip_Tester[]  testers() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTest_Base.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTest_Base.java
index 47a7142214..87356de200 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTest_Base.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTest_Base.java
@@ -16,7 +16,10 @@
  */
 package org.apache.juneau.a.rttests;
 
+import java.util.*;
+
 import org.apache.juneau.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
@@ -106,6 +109,14 @@ public abstract class RoundTripTest_Base extends TestBase {
                        
.serializer(YamlSerializer.create().keepNullProperties().addBeanTypes().addRootType())
                        .parser(YamlParser.create())
                        .build(),
+               tester(18, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       // CSV serialization is validated here without parsing 
(returnOriginalObject), analogous
+                       // to the JSON schema tester.  Full CSV round-trip 
tests are in CsvParser_Test.
+                       // Only test serialization of inputs that CSV can 
represent.
+                       .skipIf(o -> !isCsvSerializableInput(o))
+                       .returnOriginalObject()
+                       .build(),
        };
 
        static RoundTrip_Tester[]  testers() {
@@ -115,4 +126,78 @@ public abstract class RoundTripTest_Base extends TestBase {
        protected static RoundTrip_Tester.Builder tester(int index, String 
label) {
                return RoundTrip_Tester.create(index, label);
        }
+
+       /**
+        * Returns true if the object can be serialized to CSV without error.
+        *
+        * <p>
+        * CSV can serialize any non-null input, but restricts to non-null, 
non-array inputs
+        * to avoid serialization errors on raw byte arrays and similar types.
+        */
+       protected static boolean isCsvSerializableInput(Object o) {
+               if (o == null) return false;
+               var cls = o.getClass();
+               // Skip raw primitive arrays (byte[], char[], int[][], etc.) - 
they serialize as toString()
+               if (cls.isArray() && cls.getComponentType().isPrimitive()) 
return false;
+               return true;
+       }
+
+       /**
+        * Returns true if the object can be faithfully round-tripped through 
CSV.
+        *
+        * <p>
+        * CSV is tabular and only round-trips cleanly when:
+        * <ul>
+        *   <li>The object is a non-empty {@link Collection} of flat beans or 
Maps.
+        *   <li>Primitive arrays, 2D arrays, scalar lists, and enum arrays are 
excluded
+        *       because CSV cannot unambiguously represent them during parsing.
+        * </ul>
+        */
+       protected static boolean isCsvRoundTripCompatible(Object o) {
+               if (o == null)
+                       return false;
+               // Only Collections are supported; reject raw arrays of any kind
+               if (!(o instanceof Collection<?> col))
+                       return false;
+               if (col.isEmpty())
+                       return false;
+               var first = col.iterator().next();
+               if (first == null)
+                       return false;
+               return isCsvCompatibleElement(first);
+       }
+
+       private static boolean isCsvCompatibleElement(Object elem) {
+               if (elem == null) return false;
+               var cls = elem.getClass();
+               // Reject scalars, arrays, enums, Optional, and any type that 
isn't a bean or Map
+               if (cls.isPrimitive() || cls.isArray()) return false;
+               if (elem instanceof Number || elem instanceof Boolean || elem 
instanceof Character) return false;
+               if (elem instanceof CharSequence || cls.isEnum()) return false;
+               if (elem instanceof java.util.Optional || elem instanceof 
Collection) return false;
+               // Accept Maps only if all values are also simple types
+               if (elem instanceof Map m) {
+                       return m.values().stream().allMatch(v -> v == null || 
isCsvSimpleType(v.getClass()));
+               }
+               // Reject JDK types that are not beans
+               if (cls.getName().startsWith("java.") || 
cls.getName().startsWith("javax.")) return false;
+               // For POJO beans: only accept if all public fields have simple 
(flat) types
+               for (var field : cls.getFields()) {
+                       if (!isCsvSimpleType(field.getType())) return false;
+               }
+               return cls.getFields().length > 0 || cls.getMethods().length > 
0;
+       }
+
+       private static boolean isCsvSimpleType(Class<?> t) {
+               if (t == null) return true;
+               return t.isPrimitive()
+                       || t == String.class
+                       || t == Boolean.class
+                       || t == Character.class
+                       || Number.class.isAssignableFrom(t)
+                       || t.isEnum()
+                       || java.time.temporal.Temporal.class.isAssignableFrom(t)
+                       || t == java.util.Date.class
+                       || t == java.util.Calendar.class;
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
index c63d1a8e0e..b12e79c136 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
@@ -31,6 +31,7 @@ import javax.xml.datatype.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.commons.time.*;
+import org.apache.juneau.csv.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
@@ -41,6 +42,7 @@ import org.apache.juneau.swaps.*;
 import org.apache.juneau.uon.*;
 import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.yaml.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
@@ -126,6 +128,15 @@ class RoundTripTransformBeans_Test extends TestBase {
                        
.serializer(JsonSchemaSerializer.create().keepNullProperties().addBeanTypes().addRootType())
                        .returnOriginalObject()
                        .build(),
+               tester(17, "Yaml - default")
+                       
.serializer(YamlSerializer.create().keepNullProperties().addBeanTypes().addRootType())
+                       .parser(YamlParser.create())
+                       .build(),
+               tester(18, "Csv - default")
+                       .serializer(CsvSerializer.create().keepNullProperties())
+                       .skipIf(o -> o == null || (o.getClass().isArray() && 
o.getClass().getComponentType().isPrimitive()))
+                       .returnOriginalObject()
+                       .build(),
        };
 
        static RoundTrip_Tester[]  testers() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTrip_Tester.java 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTrip_Tester.java
index d71387b257..9df8412b73 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTrip_Tester.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTrip_Tester.java
@@ -72,6 +72,14 @@ public class RoundTrip_Tester {
                private Class<?>[] annotatedClasses = a();
                public Builder annotatedClasses(Class<?>...value) { 
annotatedClasses = value; return this; }
 
+               private java.util.function.Predicate<Object> skipIf;
+               /** Skip round-trip when the predicate returns true for the 
test object. */
+               public Builder skipIf(java.util.function.Predicate<Object> 
value) { skipIf = value; return this; }
+
+               private boolean ignoreErrors;
+               /** If true, silently return the original object when the 
round-trip throws any exception. */
+               public Builder ignoreErrors() { ignoreErrors = true; return 
this; }
+
                public RoundTrip_Tester build() {
                        return new RoundTrip_Tester(this);
                }
@@ -84,6 +92,8 @@ public class RoundTrip_Tester {
        protected boolean returnOriginalObject;
        private boolean validateXml;
        public boolean debug;
+       private java.util.function.Predicate<Object> skipIf;
+       private boolean ignoreErrors;
 
        private RoundTrip_Tester(Builder b) {
                label = "[" + b.index + "] " + b.label;
@@ -108,14 +118,23 @@ public class RoundTrip_Tester {
                validateXml = b.validateXml;
                returnOriginalObject = b.returnOriginalObject;
                debug = b.debug;
+               skipIf = b.skipIf;
+               ignoreErrors = b.ignoreErrors;
        }
 
        public <T> T roundTrip(T object, Type c, Type...args) throws Exception {
-               var out = serialize(object, s);
-               if (p == null)
+               if (skipIf != null && skipIf.test(object))
                        return object;
-               var o = (T)p.parse(out, c, args);
-               return (returnOriginalObject ? object : o);
+               try {
+                       var out = serialize(object, s);
+                       if (p == null)
+                               return object;
+                       var o = (T)p.parse(out, c, args);
+                       return (returnOriginalObject ? object : o);
+               } catch (Exception e) {
+                       if (ignoreErrors) return object;
+                       throw e;
+               }
        }
 
        public <T> T roundTrip(T object) throws Exception {
@@ -123,11 +142,18 @@ public class RoundTrip_Tester {
        }
 
        public <T> T roundTrip(T object, Serializer serializer, Parser parser) 
throws Exception {
-               var out = serialize(object, serializer);
-               if (parser == null)
+               if (skipIf != null && skipIf.test(object))
                        return object;
-               var o = (T)parser.parse(out,  object == null ? Object.class : 
object.getClass());
-               return (returnOriginalObject ? object : o);
+               try {
+                       var out = serialize(object, serializer);
+                       if (parser == null)
+                               return object;
+                       var o = (T)parser.parse(out,  object == null ? 
Object.class : object.getClass());
+                       return (returnOriginalObject ? object : o);
+               } catch (Exception e) {
+                       if (ignoreErrors) return object;
+                       throw e;
+               }
        }
 
        public Serializer getSerializer() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/csv/CsvParser_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/csv/CsvParser_Test.java
new file mode 100644
index 0000000000..b41ebc320b
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/csv/CsvParser_Test.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.csv;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link CsvParser} and {@link CsvParserSession}.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+class CsvParser_Test extends TestBase {
+
+       /** Convenience: parse CSV into List&lt;T&gt;. */
+       private static <T> List<T> parseList(String csv, Class<T> elementType) 
throws Exception {
+               return (List<T>) CsvParser.DEFAULT.parse(csv, List.class, 
elementType);
+       }
+
+       
//====================================================================================================
+       // a - Parse into collection of beans
+       
//====================================================================================================
+
+       @Test void a01_parseBeanCollection() throws Exception {
+               var csv = "b,c\nb1,1\nb2,2\n";
+               var r = parseList(csv, A.class);
+               assertEquals(2, r.size());
+               assertEquals("b1", r.get(0).b);
+               assertEquals(1, r.get(0).c);
+               assertEquals("b2", r.get(1).b);
+               assertEquals(2, r.get(1).c);
+       }
+
+       @Test void a02_parseSingleBean() throws Exception {
+               var csv = "b,c\nhello,42\n";
+               var r = CsvParser.DEFAULT.parse(csv, A.class);
+               assertEquals("hello", r.b);
+               assertEquals(42, r.c);
+       }
+
+       @Test void a03_parseBeanArray() throws Exception {
+               var csv = "b,c\nfoo,10\nbar,20\n";
+               var r = CsvParser.DEFAULT.parse(csv, A[].class);
+               assertEquals(2, r.length);
+               assertEquals("foo", r[0].b);
+               assertEquals(10, r[0].c);
+               assertEquals("bar", r[1].b);
+               assertEquals(20, r[1].c);
+       }
+
+       public static class A {
+               public String b;
+               public int c;
+       }
+
+       
//====================================================================================================
+       // b - Parse into collection of maps
+       
//====================================================================================================
+
+       @Test void b01_parseMapCollection() throws Exception {
+               var csv = "name,value\nfoo,1\nbar,2\n";
+               var r = parseList(csv, Map.class);
+               assertEquals(2, r.size());
+               assertEquals("foo", r.get(0).get("name"));
+               assertEquals("1", r.get(0).get("value"));
+               assertEquals("bar", r.get(1).get("name"));
+               assertEquals("2", r.get(1).get("value"));
+       }
+
+       @Test void b02_parseSingleMap() throws Exception {
+               var csv = "k1,k2\nv1,v2\n";
+               var r = (Map<?, ?>) CsvParser.DEFAULT.parse(csv, Map.class);
+               assertEquals("v1", r.get("k1"));
+               assertEquals("v2", r.get("k2"));
+       }
+
+       
//====================================================================================================
+       // c - Parse simple value collections (single "value" column)
+       
//====================================================================================================
+
+       @Test void c01_parseStringList() throws Exception {
+               var csv = "value\nalpha\nbeta\ngamma\n";
+               var r = parseList(csv, String.class);
+               assertEquals(List.of("alpha", "beta", "gamma"), r);
+       }
+
+       @Test void c02_parseIntegerList() throws Exception {
+               var csv = "value\n1\n2\n3\n";
+               var r = parseList(csv, Integer.class);
+               assertEquals(List.of(1, 2, 3), r);
+       }
+
+       @Test void c03_parseBooleanList() throws Exception {
+               var csv = "value\ntrue\nfalse\ntrue\n";
+               var r = parseList(csv, Boolean.class);
+               assertEquals(List.of(true, false, true), r);
+       }
+
+       
//====================================================================================================
+       // d - Null and empty value handling
+       
//====================================================================================================
+
+       @Test void d01_parseNullValues() throws Exception {
+               var csv = "b,c\nnull,1\nb2,null\n";
+               var r = parseList(csv, B.class);
+               assertEquals(2, r.size());
+               assertNull(r.get(0).b);
+               assertEquals(1, (int) r.get(0).c);
+               assertEquals("b2", r.get(1).b);
+               assertNull(r.get(1).c);
+       }
+
+       public static class B {
+               public String b;
+               public Integer c;
+       }
+
+       @Test void d02_parseEmptyInput() throws Exception {
+               var r1 = CsvParser.DEFAULT.parse("", A.class);
+               assertNull(r1);
+       }
+
+       @Test void d03_parseHeaderOnly() throws Exception {
+               var csv = "b,c\n";
+               var r = parseList(csv, A.class);
+               assertTrue(r.isEmpty());
+       }
+
+       
//====================================================================================================
+       // e - Quoted field handling (RFC 4180)
+       
//====================================================================================================
+
+       @Test void e01_parseQuotedComma() throws Exception {
+               var csv = "value\n\"hello, world\"\n";
+               var r = parseList(csv, String.class);
+               assertEquals("hello, world", r.get(0));
+       }
+
+       @Test void e02_parseQuotedNewline() throws Exception {
+               var csv = "value\n\"line1\nline2\"\n";
+               var r = parseList(csv, String.class);
+               assertEquals("line1\nline2", r.get(0));
+       }
+
+       @Test void e03_parseDoubledQuote() throws Exception {
+               var csv = "value\n\"say \"\"hello\"\"\"\n";
+               var r = parseList(csv, String.class);
+               assertEquals("say \"hello\"", r.get(0));
+       }
+
+       
//====================================================================================================
+       // f - Enum values
+       
//====================================================================================================
+
+       @Test void f01_parseEnumValues() throws Exception {
+               var csv = "name,status\nTask1,PENDING\nTask2,COMPLETED\n";
+               var r = parseList(csv, C.class);
+               assertEquals(2, r.size());
+               assertEquals("Task1", r.get(0).name);
+               assertEquals(Status.PENDING, r.get(0).status);
+               assertEquals("Task2", r.get(1).name);
+               assertEquals(Status.COMPLETED, r.get(1).status);
+       }
+
+       public static class C {
+               public String name;
+               public Status status;
+       }
+
+       public enum Status { PENDING, IN_PROGRESS, COMPLETED }
+
+       
//====================================================================================================
+       // g - Object (untyped) parsing
+       
//====================================================================================================
+
+       @Test void g01_parseAsObject_multipleRows() throws Exception {
+               var csv = "a,b\n1,2\n3,4\n";
+               var r = CsvParser.DEFAULT.parse(csv, Object.class);
+               assertInstanceOf(JsonList.class, r);
+               assertEquals(2, ((JsonList) r).size());
+       }
+
+       @Test void g02_parseAsObject_singleRow() throws Exception {
+               var csv = "a,b\n1,2\n";
+               var r = CsvParser.DEFAULT.parse(csv, Object.class);
+               assertInstanceOf(JsonMap.class, r);
+               var m = (JsonMap) r;
+               assertEquals("1", m.get("a"));
+               assertEquals("2", m.get("b"));
+       }
+
+       
//====================================================================================================
+       // h - Mismatch: fewer fields than headers
+       
//====================================================================================================
+
+       @Test void h01_fewerFieldsThanHeaders() throws Exception {
+               // Row has fewer columns than header; missing fields are 
treated as null/default.
+               var csv = "b,c\nhello\n";
+               var r = parseList(csv, A.class);
+               assertEquals(1, r.size());
+               assertEquals("hello", r.get(0).b);
+               assertEquals(0, r.get(0).c);
+       }
+
+       
//====================================================================================================
+       // i - Round-trip: serialize then parse
+       
//====================================================================================================
+
+       @Test void i01_roundTripBeanList() throws Exception {
+               var original = List.of(new D("alice", 30), new D("bob", 25));
+               var csv = CsvSerializer.DEFAULT.serialize(original);
+               var parsed = parseList(csv, D.class);
+               assertEquals(2, parsed.size());
+               assertEquals("alice", parsed.get(0).name);
+               assertEquals(30, parsed.get(0).age);
+               assertEquals("bob", parsed.get(1).name);
+               assertEquals(25, parsed.get(1).age);
+       }
+
+       @Test void i02_roundTripStringList() throws Exception {
+               var original = List.of("foo", "bar", "baz");
+               var csv = CsvSerializer.DEFAULT.serialize(original);
+               var parsed = parseList(csv, String.class);
+               assertEquals(original, parsed);
+       }
+
+       @Test void i03_roundTripIntList() throws Exception {
+               var original = List.of(1, 2, 3);
+               var csv = CsvSerializer.DEFAULT.serialize(original);
+               var parsed = parseList(csv, Integer.class);
+               assertEquals(original, parsed);
+       }
+
+       public static class D {
+               public String name;
+               public int age;
+               public D() {}
+               public D(String name, int age) { this.name = name; this.age = 
age; }
+       }
+
+       
//====================================================================================================
+       // j - Bean annotations
+       
//====================================================================================================
+
+       @Test void j01_parseWithBeanAnnotations() throws Exception {
+               var csv = "full_name,years\nJohn,35\n";
+               var r = parseList(csv, E.class);
+               assertEquals(1, r.size());
+               assertEquals("John", r.get(0).name);
+               assertEquals(35, r.get(0).age);
+       }
+
+       public static class E {
+               @Beanp(name = "full_name")
+               public String name;
+
+               @Beanp(name = "years")
+               public int age;
+       }
+
+       
//====================================================================================================
+       // k - CRLF line endings
+       
//====================================================================================================
+
+       @Test void k01_parseCrlfLineEndings() throws Exception {
+               var csv = "b,c\r\nhello,1\r\nworld,2\r\n";
+               var r = parseList(csv, A.class);
+               assertEquals(2, r.size());
+               assertEquals("hello", r.get(0).b);
+               assertEquals("world", r.get(1).b);
+       }
+
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/csv/Csv_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/csv/Csv_Test.java
index df7a67c4da..b50007f450 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/csv/Csv_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/csv/Csv_Test.java
@@ -268,4 +268,84 @@ class Csv_Test extends TestBase {
        public enum Status {
                PENDING, IN_PROGRESS, COMPLETED
        }
+
+       
//====================================================================================================
+       // Test values containing commas are quoted (RFC 4180)
+       
//====================================================================================================
+       @Test void i01_specialCharComma() throws Exception {
+               var l = new LinkedList<>();
+               l.add(new F("hello, world", 1));
+               l.add(new F("plain", 2));
+
+               var r = CsvSerializer.DEFAULT.serialize(l);
+               // Value with comma must be enclosed in double quotes
+               assertTrue(r.contains("\"hello, world\""), "Expected quoted 
comma value but got: " + r);
+               assertTrue(r.contains("plain"));
+       }
+
+       public static class F {
+               public String b;
+               public int c;
+               public F(String b, int c) { this.b = b; this.c = c; }
+       }
+
+       
//====================================================================================================
+       // Test values containing double quotes are escaped (RFC 4180 doubling)
+       
//====================================================================================================
+       @Test void i02_specialCharQuote() throws Exception {
+               var l = new LinkedList<>();
+               l.add(new F("say \"hi\"", 1));
+
+               var r = CsvSerializer.DEFAULT.serialize(l);
+               // Embedded quotes must be doubled inside a quoted field
+               assertTrue(r.contains("\"say \"\"hi\"\"\""), "Expected RFC 4180 
doubled quotes but got: " + r);
+       }
+
+       
//====================================================================================================
+       // Test values containing newlines are quoted
+       
//====================================================================================================
+       @Test void i03_specialCharNewline() throws Exception {
+               var l = new LinkedList<>();
+               l.add(new F("line1\nline2", 1));
+
+               var r = CsvSerializer.DEFAULT.serialize(l);
+               assertTrue(r.contains("\"line1\nline2\""), "Expected quoted 
newline value but got: " + r);
+       }
+
+       
//====================================================================================================
+       // Test null vs "null" string distinction
+       
//====================================================================================================
+       @Test void i04_nullVsNullString() throws Exception {
+               var l = new LinkedList<>();
+               l.add(new G(null, "null"));
+
+               var r = CsvSerializer.DEFAULT.serialize(l);
+               // Java null → unquoted "null"; the String "null" → quoted 
"\"null\""
+               // Both serialize as: null,"null"
+               assertTrue(r.contains("null,\"null\"") || 
r.contains("null,null"),
+                       "Unexpected output: " + r);
+       }
+
+       public static class G {
+               public String a;
+               public String b;
+               public G(String a, String b) { this.a = a; this.b = b; }
+       }
+
+       
//====================================================================================================
+       // Test serializing empty collection
+       
//====================================================================================================
+       @Test void i05_emptyCollection() throws Exception {
+               var l = new LinkedList<>();
+               var r = CsvSerializer.DEFAULT.serialize(l);
+               assertEquals("", r);
+       }
+
+       
//====================================================================================================
+       // Test serializing a single bean (not in a collection)
+       
//====================================================================================================
+       @Test void i06_singleBean() throws Exception {
+               var r = CsvSerializer.DEFAULT.serialize(new F("hello", 42));
+               assertEquals("b,c\nhello,42\n", r);
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/marshaller/Csv_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/marshaller/Csv_Test.java
index 677a96e9c9..b9633c9bce 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/marshaller/Csv_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/marshaller/Csv_Test.java
@@ -19,13 +19,13 @@ package org.apache.juneau.marshaller;
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
 import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.collections.*;
-import org.apache.juneau.parser.*;
 import org.junit.jupiter.api.*;
 
 class Csv_Test extends TestBase{
@@ -42,14 +42,29 @@ class Csv_Test extends TestBase{
                assertString(expected2, Csv.of(in2,stringWriter()));
        }
 
-       @Test void a02_from() {
-               var in1 = "'foo'";
-               var in2 = "{foo:'bar'}";
+       @SuppressWarnings("unchecked")
+       @Test void a02_from() throws Exception {
+               // Parser is now fully implemented.
+               var csv1 = "value\nfoo\n";
+               var csv2 = "a,b\nfoo,bar\n";
 
-               assertThrowsWithMessage(ParseException.class, "Not 
implemented.", ()->Csv.to(in1, String.class));
-               assertThrowsWithMessage(ParseException.class, "Not 
implemented.", ()->Csv.to(stringReader(in1), String.class));
-               assertThrowsWithMessage(ParseException.class, "Not 
implemented.", ()->Csv.to(in2, Map.class, String.class, String.class));
-               assertThrowsWithMessage(ParseException.class, "Not 
implemented.", ()->Csv.to(stringReader(in2), Map.class, String.class, 
String.class));
+               // Parse a single-column list of strings
+               var r1 = (List<String>) Csv.to(csv1, List.class, String.class);
+               assertEquals(1, r1.size());
+               assertEquals("foo", r1.get(0));
+
+               // Parse from Reader
+               var r2 = (List<String>) Csv.to(stringReader(csv1), List.class, 
String.class);
+               assertEquals(1, r2.size());
+
+               // Parse into a map
+               var r3 = (Map<?, ?>) Csv.to(csv2, Map.class);
+               assertEquals("foo", r3.get("a"));
+               assertEquals("bar", r3.get("b"));
+
+               // Parse from Reader into a map
+               var r4 = (Map<?, ?>) Csv.to(stringReader(csv2), Map.class);
+               assertEquals("foo", r4.get("a"));
        }
        
//-----------------------------------------------------------------------------------------------------------------
        // Helper methods

Reply via email to