Simplify types, replace Value with Variant.

Change-Id: I2e492ebfefc7e9a47c874ed22ff199412e9948ee
diff --git a/README.md b/README.md
index e482857..dead043 100644
--- a/README.md
+++ b/README.md
@@ -24,17 +24,17 @@
 To parse a [GVariant][] value of type `"a(si)"`, which is an array of
 pairs of [String][] and `int`, you can use the following code:
 
-    record ExampleRecord(Value.Str s, Value.Int32 i) {}
+    record ExampleRecord(String s, int i) {}
     
     var decoder =
       Decoder.ofArray(
         Decoder.ofStructure(
           ExampleRecord.class,
-          Decoder.ofStr(StandardCharsets.UTF_8),
-          Decoder.ofInt32().withByteOrder(ByteOrder.LITTLE_ENDIAN)));
+          Decoder.ofString(StandardCharsets.UTF_8),
+          Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN)));
     
     byte[] bytes = ...;
-    Value.Array<Value.Structure<ExampleRecord>> example = decoder.decode(bytes);
+    List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes));
 
 
 [ByteBuffer]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/ByteBuffer.html
diff --git a/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
index c51a723..8134d45 100644
--- a/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
@@ -2,17 +2,6 @@
 
 import static java.nio.ByteOrder.LITTLE_ENDIAN;
 
-import eu.mulk.jgvariant.core.Value.Array;
-import eu.mulk.jgvariant.core.Value.Bool;
-import eu.mulk.jgvariant.core.Value.Float64;
-import eu.mulk.jgvariant.core.Value.Int16;
-import eu.mulk.jgvariant.core.Value.Int32;
-import eu.mulk.jgvariant.core.Value.Int64;
-import eu.mulk.jgvariant.core.Value.Int8;
-import eu.mulk.jgvariant.core.Value.Maybe;
-import eu.mulk.jgvariant.core.Value.Str;
-import eu.mulk.jgvariant.core.Value.Structure;
-import eu.mulk.jgvariant.core.Value.Variant;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.RecordComponent;
 import java.nio.ByteBuffer;
@@ -25,7 +14,7 @@
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Type class for decodable {@link Value} types.
+ * Type class for decodable {@link Variant} types.
  *
  * <p>Use the {@code of*} family of constructor methods to acquire a suitable {@link Decoder} for
  * the type you wish to decode.
@@ -36,23 +25,23 @@
  * {@code int}, you can use the following code:
  *
  * <pre>{@code
- * record ExampleRecord(Value.Str s, Value.Int32 i) {}
+ * record ExampleRecord(String s, int i) {}
  *
  * var decoder =
  *   Decoder.ofArray(
  *     Decoder.ofStructure(
  *       ExampleRecord.class,
- *       Decoder.ofStr(UTF_8),
- *       Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN)));
+ *       Decoder.ofString(UTF_8),
+ *       Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
  *
  * byte[] bytes = ...;
- * Value.Array<Value.Structure<ExampleRecord>> example = decoder.decode(ByteBuffer.wrap(bytes));
+ * List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes));
  * }</pre>
  *
  * @param <T> the type that the {@link Decoder} can decode.
  */
 @SuppressWarnings("java:S1610")
-public abstract class Decoder<T extends Value> {
+public abstract class Decoder<T> {
 
   private Decoder() {}
 
@@ -100,13 +89,13 @@
   }
 
   /**
-   * Creates a {@link Decoder} for an {@link Array} type.
+   * Creates a {@link Decoder} for an {@code Array} type.
    *
    * @param elementDecoder a {@link Decoder} for the elements of the array.
    * @param <U> the element type.
    * @return a new {@link Decoder}.
    */
-  public static <U extends Value> Decoder<Array<U>> ofArray(Decoder<U> elementDecoder) {
+  public static <U> Decoder<List<U>> ofArray(Decoder<U> elementDecoder) {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -120,7 +109,7 @@
       }
 
       @Override
-      public Array<U> decode(ByteBuffer byteSlice) {
+      public List<U> decode(ByteBuffer byteSlice) {
         List<U> elements;
 
         var elementSize = elementDecoder.fixedSize();
@@ -150,7 +139,7 @@
           }
         }
 
-        return new Array<>(elements);
+        return elements;
       }
     };
   }
@@ -171,13 +160,13 @@
   }
 
   /**
-   * Creates a {@link Decoder} for a {@link Maybe} type.
+   * Creates a {@link Decoder} for a {@code Maybe} type.
    *
    * @param elementDecoder a {@link Decoder} for the contained element.
    * @param <U> the element type.
    * @return a new {@link Decoder}.
    */
-  public static <U extends Value> Decoder<Maybe<U>> ofMaybe(Decoder<U> elementDecoder) {
+  public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -191,32 +180,31 @@
       }
 
       @Override
-      public Maybe<U> decode(ByteBuffer byteSlice) {
+      public Optional<U> decode(ByteBuffer byteSlice) {
         if (!byteSlice.hasRemaining()) {
-          return new Maybe<>(Optional.empty());
+          return Optional.empty();
         } else {
           if (!elementDecoder.hasFixedSize()) {
             // Remove trailing zero byte.
             byteSlice.limit(byteSlice.limit() - 1);
           }
 
-          return new Maybe<>(Optional.of(elementDecoder.decode(byteSlice)));
+          return Optional.of(elementDecoder.decode(byteSlice));
         }
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for a {@link Structure} type.
+   * Creates a {@link Decoder} for a {@code Structure} type.
    *
    * @param recordType the {@link Record} type that represents the components of the structure.
    * @param componentDecoders a {@link Decoder} for each component of the structure.
    * @param <U> the {@link Record} type that represents the components of the structure.
    * @return a new {@link Decoder}.
    */
-  @SafeVarargs
-  public static <U extends Record> Decoder<Structure<U>> ofStructure(
-      Class<U> recordType, Decoder<? extends Value>... componentDecoders) {
+  public static <U extends Record> Decoder<U> ofStructure(
+      Class<U> recordType, Decoder<?>... componentDecoders) {
     var recordComponents = recordType.getRecordComponents();
     if (componentDecoders.length != recordComponents.length) {
       throw new IllegalArgumentException(
@@ -251,7 +239,7 @@
       }
 
       @Override
-      public Structure<U> decode(ByteBuffer byteSlice) {
+      public U decode(ByteBuffer byteSlice) {
         int framingOffsetSize = byteCount(byteSlice.limit());
 
         var recordConstructorArguments = new Object[recordComponents.length];
@@ -296,7 +284,7 @@
                   .map(RecordComponent::getType)
                   .toArray(Class<?>[]::new);
           var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
-          return new Structure<>(recordConstructor.newInstance(recordConstructorArguments));
+          return recordConstructor.newInstance(recordConstructorArguments);
         } catch (NoSuchMethodException
             | InstantiationException
             | IllegalAccessException
@@ -334,11 +322,11 @@
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Bool} type.
+   * Creates a {@link Decoder} for the {@code Boolean} type.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Bool> ofBool() {
+  public static Decoder<Boolean> ofBoolean() {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -351,21 +339,21 @@
       }
 
       @Override
-      public Bool decode(ByteBuffer byteSlice) {
-        return new Bool(byteSlice.get() != 0);
+      public Boolean decode(ByteBuffer byteSlice) {
+        return byteSlice.get() != 0;
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Int8} type.
+   * Creates a {@link Decoder} for the 8-bit {@ode byte} type.
    *
    * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
    * result of this method.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Int8> ofInt8() {
+  public static Decoder<Byte> ofByte() {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -378,21 +366,21 @@
       }
 
       @Override
-      public Int8 decode(ByteBuffer byteSlice) {
-        return new Int8(byteSlice.get());
+      public Byte decode(ByteBuffer byteSlice) {
+        return byteSlice.get();
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Int16} type.
+   * Creates a {@link Decoder} for the 16-bit {@code short} type.
    *
    * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
    * result of this method.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Int16> ofInt16() {
+  public static Decoder<Short> ofShort() {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -405,21 +393,21 @@
       }
 
       @Override
-      public Int16 decode(ByteBuffer byteSlice) {
-        return new Int16(byteSlice.getShort());
+      public Short decode(ByteBuffer byteSlice) {
+        return byteSlice.getShort();
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Int32} type.
+   * Creates a {@link Decoder} for the 32-bit {@code int} type.
    *
    * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
    * result of this method.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Int32> ofInt32() {
+  public static Decoder<Integer> ofInt() {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -432,21 +420,21 @@
       }
 
       @Override
-      public Int32 decode(ByteBuffer byteSlice) {
-        return new Int32(byteSlice.getInt());
+      public Integer decode(ByteBuffer byteSlice) {
+        return byteSlice.getInt();
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Int64} type.
+   * Creates a {@link Decoder} for the 64-bit {@code long} type.
    *
    * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
    * result of this method.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Int64> ofInt64() {
+  public static Decoder<Long> ofLong() {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -459,18 +447,18 @@
       }
 
       @Override
-      public Int64 decode(ByteBuffer byteSlice) {
-        return new Int64(byteSlice.getLong());
+      public Long decode(ByteBuffer byteSlice) {
+        return byteSlice.getLong();
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Float64} type.
+   * Creates a {@link Decoder} for the {@code double} type.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Float64> ofFloat64() {
+  public static Decoder<Double> ofDouble() {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -483,21 +471,21 @@
       }
 
       @Override
-      public Float64 decode(ByteBuffer byteSlice) {
-        return new Float64(byteSlice.getDouble());
+      public Double decode(ByteBuffer byteSlice) {
+        return byteSlice.getDouble();
       }
     };
   }
 
   /**
-   * Creates a {@link Decoder} for the {@link Str} type.
+   * Creates a {@link Decoder} for the {@link String} type.
    *
    * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
    * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
    *
    * @return a new {@link Decoder}.
    */
-  public static Decoder<Str> ofStr(Charset charset) {
+  public static Decoder<String> ofString(Charset charset) {
     return new Decoder<>() {
       @Override
       public byte alignment() {
@@ -511,9 +499,9 @@
       }
 
       @Override
-      public Str decode(ByteBuffer byteSlice) {
+      public String decode(ByteBuffer byteSlice) {
         byteSlice.limit(byteSlice.limit() - 1);
-        return new Str(charset.decode(byteSlice).toString());
+        return charset.decode(byteSlice).toString();
       }
     };
   }
diff --git a/src/main/java/eu/mulk/jgvariant/core/Value.java b/src/main/java/eu/mulk/jgvariant/core/Value.java
deleted file mode 100644
index 54c0b79..0000000
--- a/src/main/java/eu/mulk/jgvariant/core/Value.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package eu.mulk.jgvariant.core;
-
-import java.util.List;
-import java.util.Optional;
-
-/**
- * A value representable by the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
- * serialization format.
- *
- * <p>{@link Value} is a sum type (sealed interface) that represents a GVariant value. Its subtypes
- * represent the different types of values that GVariant supports.
- *
- * @see Decoder
- */
-public sealed interface Value {
-
-  /**
-   * A homogeneous sequence of GVariant values.
-   *
-   * <p>Arrays of fixed width (i.e. of values of fixed size) are represented in a similar way to
-   * plain C arrays. Arrays of variable width require additional space for padding and framing.
-   *
-   * <p>Heterogeneous sequences are represented by {@code Array<Variant>}.
-   *
-   * @param <T> the type of the elements of the array.
-   * @see Decoder#ofArray
-   */
-  record Array<T extends Value>(List<T> values) implements Value {}
-
-  /**
-   * A value that is either present or absent.
-   *
-   * @param <T> the contained type.
-   * @see Decoder#ofMaybe
-   */
-  record Maybe<T extends Value>(Optional<T> value) implements Value {}
-
-  /**
-   * A tuple of values of fixed types.
-   *
-   * <p>GVariant structures are represented as {@link Record} types. For example, a two-element
-   * structure consisting of a string and an int can be modelled as follows:
-   *
-   * <pre>{@code
-   * record TestRecord(Str s, Int32 i) {}
-   * var testStruct = new Structure<>(new TestRecord(new Str("hello"), new Int32(123));
-   * }</pre>
-   *
-   * @param <T> the {@link Record} type that represents the components of the structure.
-   * @see Decoder#ofStructure
-   */
-  record Structure<T extends Record>(T values) implements Value {}
-
-  /**
-   * A dynamically typed box that can hold a single value of any GVariant type.
-   *
-   * @see Decoder#ofVariant
-   */
-  record Variant(Class<? extends Value> type, Value value) implements Value {}
-
-  /**
-   * Either true or false.
-   *
-   * @see Decoder#ofBool()
-   */
-  record Bool(boolean value) implements Value {
-    static Bool TRUE = new Bool(true);
-    static Bool FALSE = new Bool(false);
-  }
-
-  /**
-   * A {@code byte}-sized integer.
-   *
-   * @see Decoder#ofInt8()
-   */
-  record Int8(byte value) implements Value {}
-
-  /**
-   * A {@code short}-sized integer.
-   *
-   * @see Decoder#ofInt16()
-   */
-  record Int16(short value) implements Value {}
-
-  /**
-   * An {@code int}-sized integer.
-   *
-   * @see Decoder#ofInt32()
-   */
-  record Int32(int value) implements Value {}
-
-  /**
-   * A {@code long}-sized integer.
-   *
-   * @see Decoder#ofInt64()
-   */
-  record Int64(long value) implements Value {}
-
-  /**
-   * A double-precision floating point number.
-   *
-   * @see Decoder#ofFloat64()
-   */
-  record Float64(double value) implements Value {}
-
-  /**
-   * A character string.
-   *
-   * @see Decoder#ofStr
-   */
-  record Str(String value) implements Value {}
-}
diff --git a/src/main/java/eu/mulk/jgvariant/core/Variant.java b/src/main/java/eu/mulk/jgvariant/core/Variant.java
new file mode 100644
index 0000000..05e28d5
--- /dev/null
+++ b/src/main/java/eu/mulk/jgvariant/core/Variant.java
@@ -0,0 +1,102 @@
+package eu.mulk.jgvariant.core;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A value representable by the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
+ * serialization format, tagged with its type.
+ *
+ * <p>{@link Variant} is a sum type (sealed interface) that represents a GVariant value. Its
+ * subtypes represent the different types of values that GVariant supports.
+ *
+ * @see Decoder#ofVariant()
+ */
+public sealed interface Variant {
+
+  /**
+   * A homogeneous sequence of GVariant values.
+   *
+   * <p>Arrays of fixed width (i.e. of values of fixed size) are represented in a similar way to
+   * plain C arrays. Arrays of variable width require additional space for padding and framing.
+   *
+   * <p>Heterogeneous sequences are represented by {@code Array<Variant>}.
+   *
+   * @param <T> the type of the elements of the array.
+   * @see Decoder#ofArray
+   */
+  record Array<T>(List<T> values) implements Variant {}
+
+  /**
+   * A value that is either present or absent.
+   *
+   * @param <T> the contained type.
+   * @see Decoder#ofMaybe
+   */
+  record Maybe<T>(Optional<T> value) implements Variant {}
+
+  /**
+   * A tuple of values of fixed types.
+   *
+   * <p>GVariant structures are represented as {@link Record} types. For example, a two-element
+   * structure consisting of a string and an int can be modelled as follows:
+   *
+   * <pre>{@code
+   * record TestRecord(String s, int i) {}
+   * var testStruct = new Structure<>(new TestRecord("hello", 123);
+   * }</pre>
+   *
+   * @param <T> the {@link Record} type that represents the components of the structure.
+   * @see Decoder#ofStructure
+   */
+  record Structure<T extends Record>(T values) implements Variant {}
+
+  /**
+   * Either true or false.
+   *
+   * @see Decoder#ofBoolean()
+   */
+  record Bool(boolean value) implements Variant {}
+
+  /**
+   * A {@code byte}-sized integer.
+   *
+   * @see Decoder#ofByte()
+   */
+  record Byte(byte value) implements Variant {}
+
+  /**
+   * A {@code short}-sized integer.
+   *
+   * @see Decoder#ofShort()
+   */
+  record Short(short value) implements Variant {}
+
+  /**
+   * An {@code int}-sized integer.
+   *
+   * @see Decoder#ofInt()
+   */
+  record Int(int value) implements Variant {}
+
+  /**
+   * A {@code long}-sized integer.
+   *
+   * @see Decoder#ofLong()
+   */
+  record Long(long value) implements Variant {}
+
+  /**
+   * A double-precision floating point number.
+   *
+   * @see Decoder#ofDouble()
+   */
+  record Double(double value) implements Variant {}
+
+  /**
+   * A character string.
+   *
+   * @see Decoder#ofString
+   */
+  record String(java.lang.String value) implements Variant {}
+}
diff --git a/src/main/java/eu/mulk/jgvariant/core/package-info.java b/src/main/java/eu/mulk/jgvariant/core/package-info.java
index dba7a09..1b819c5 100644
--- a/src/main/java/eu/mulk/jgvariant/core/package-info.java
+++ b/src/main/java/eu/mulk/jgvariant/core/package-info.java
@@ -1,32 +1,32 @@
 /**
- * Provides {@link eu.mulk.jgvariant.core.Value} and {@link eu.mulk.jgvariant.core.Decoder}, the
+ * Provides {@link eu.mulk.jgvariant.core.Variant} and {@link eu.mulk.jgvariant.core.Decoder}, the
  * foundational classes for <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
  * parsing.
  *
- * <p>{@link eu.mulk.jgvariant.core.Value} is a sum type (sealed interface) that represents a
+ * <p>{@link eu.mulk.jgvariant.core.Variant} is a sum type (sealed interface) that represents a
  * GVariant value. Its subtypes represent the different types of values that GVariant supports.
  *
  * <p>Instances of {@link eu.mulk.jgvariant.core.Decoder} read a given concrete subtype of {@link
- * eu.mulk.jgvariant.core.Value} from a {@link java.nio.ByteBuffer}. The class also contains factory
- * methods to create those instances.
+ * eu.mulk.jgvariant.core.Variant} from a {@link java.nio.ByteBuffer}. The class also contains
+ * factory methods to create those instances.
  *
  * <p><strong>Example</strong>
  *
- * <p>To parse a GVariant of type {@code "a(si)"}, which is an array of pairs of {@link String} and
- * {@code int}, you can use the following code:
+ * <p>To parse a GVariant of type {@code "a(si)"}, which is an array of pairs of {@link
+ * java.lang.String} and {@code int}, you can use the following code:
  *
  * <pre>{@code
- * record ExampleRecord(Value.Str s, Value.Int32 i) {}
+ * record ExampleRecord(String s, int i) {}
  *
  * var decoder =
  *   Decoder.ofArray(
  *     Decoder.ofStructure(
  *       ExampleRecord.class,
- *       Decoder.ofStr(UTF_8),
- *       Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN)));
+ *       Decoder.ofString(UTF_8),
+ *       Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
  *
  * byte[] bytes = ...;
- * Value.Array<Value.Structure<ExampleRecord>> example = decoder.decode(ByteBuffer.wrap(bytes));
+ * List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes));
  * }</pre>
  */
 package eu.mulk.jgvariant.core;
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 403cf1f..af28413 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,9 +1,11 @@
+import eu.mulk.jgvariant.core.Variant;
+
 /**
  * Provides a parser for the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
  * serialization format.
  *
- * <p>The {@link eu.mulk.jgvariant.core} package contains the {@link eu.mulk.jgvariant.core.Value}
- * and {@link eu.mulk.jgvariant.core.Decoder} types. which form the foundation of this library.
+ * <p>The {@link eu.mulk.jgvariant.core} package contains the {@link Variant} and {@link
+ * eu.mulk.jgvariant.core.Decoder} types. which form the foundation of this library.
  */
 module eu.mulk.jgvariant.core {
   requires com.google.errorprone.annotations;
diff --git a/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java b/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
index 13cd398..d37f6a2 100644
--- a/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
+++ b/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
@@ -4,13 +4,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import eu.mulk.jgvariant.core.Value.Array;
-import eu.mulk.jgvariant.core.Value.Bool;
-import eu.mulk.jgvariant.core.Value.Int32;
-import eu.mulk.jgvariant.core.Value.Int8;
-import eu.mulk.jgvariant.core.Value.Maybe;
-import eu.mulk.jgvariant.core.Value.Str;
-import eu.mulk.jgvariant.core.Value.Structure;
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Optional;
@@ -25,26 +18,23 @@
   @Test
   void testString() {
     var data = new byte[] {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x00};
-    var decoder = Decoder.ofStr(UTF_8);
-    assertEquals(new Str("hello world"), decoder.decode(ByteBuffer.wrap(data)));
+    var decoder = Decoder.ofString(UTF_8);
+    assertEquals("hello world", decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
   void testMaybe() {
     var data =
         new byte[] {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x00, 0x00};
-    var decoder = Decoder.ofMaybe(Decoder.ofStr(UTF_8));
-    assertEquals(
-        new Maybe<>(Optional.of(new Str("hello world"))), decoder.decode(ByteBuffer.wrap(data)));
+    var decoder = Decoder.ofMaybe(Decoder.ofString(UTF_8));
+    assertEquals(Optional.of("hello world"), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
   void testBooleanArray() {
     var data = new byte[] {0x01, 0x00, 0x00, 0x01, 0x01};
-    var decoder = Decoder.ofArray(Decoder.ofBool());
-    assertEquals(
-        new Array<>(List.of(Bool.TRUE, Bool.FALSE, Bool.FALSE, Bool.TRUE, Bool.TRUE)),
-        decoder.decode(ByteBuffer.wrap(data)));
+    var decoder = Decoder.ofArray(Decoder.ofBoolean());
+    assertEquals(List.of(true, false, false, true, true), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
@@ -54,12 +44,10 @@
           0x66, 0x6F, 0x6F, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x04
         };
 
-    record TestRecord(Str s, Int32 i) {}
+    record TestRecord(String s, int i) {}
 
-    var decoder = Decoder.ofStructure(TestRecord.class, Decoder.ofStr(UTF_8), Decoder.ofInt32());
-    assertEquals(
-        new Structure<>(new TestRecord(new Str("foo"), new Int32(-1))),
-        decoder.decode(ByteBuffer.wrap(data)));
+    var decoder = Decoder.ofStructure(TestRecord.class, Decoder.ofString(UTF_8), Decoder.ofInt());
+    assertEquals(new TestRecord("foo", -1), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
@@ -91,19 +79,16 @@
           0x15
         };
 
-    record TestRecord(Str s, Int32 i) {}
+    record TestRecord(String s, int i) {}
 
     var decoder =
         Decoder.ofArray(
             Decoder.ofStructure(
                 TestRecord.class,
-                Decoder.ofStr(UTF_8),
-                Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN)));
+                Decoder.ofString(UTF_8),
+                Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
     assertEquals(
-        new Array<>(
-            List.of(
-                new Structure<>(new TestRecord(new Str("hi"), new Int32(-2))),
-                new Structure<>(new TestRecord(new Str("bye"), new Int32(-1))))),
+        List.of(new TestRecord("hi", -2), new TestRecord("bye", -1)),
         decoder.decode(ByteBuffer.wrap(data)));
   }
 
@@ -114,10 +99,8 @@
           0x69, 0x00, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E,
           0x67, 0x73, 0x3F, 0x00, 0x02, 0x06, 0x0a, 0x13
         };
-    var decoder = Decoder.ofArray(Decoder.ofStr(UTF_8));
-    assertEquals(
-        new Array<>(List.of(new Str("i"), new Str("can"), new Str("has"), new Str("strings?"))),
-        decoder.decode(ByteBuffer.wrap(data)));
+    var decoder = Decoder.ofArray(Decoder.ofString(UTF_8));
+    assertEquals(List.of("i", "can", "has", "strings?"), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
@@ -128,20 +111,17 @@
           0x73, 0x3F, 0x00, 0x04, 0x0d, 0x05
         };
 
-    record TestChild(Int8 b, Str s) {}
-    record TestParent(Structure<TestChild> tc, Array<Str> as) {}
+    record TestChild(byte b, String s) {}
+    record TestParent(TestChild tc, List<String> as) {}
 
     var decoder =
         Decoder.ofStructure(
             TestParent.class,
-            Decoder.ofStructure(TestChild.class, Decoder.ofInt8(), Decoder.ofStr(UTF_8)),
-            Decoder.ofArray(Decoder.ofStr(UTF_8)));
+            Decoder.ofStructure(TestChild.class, Decoder.ofByte(), Decoder.ofString(UTF_8)),
+            Decoder.ofArray(Decoder.ofString(UTF_8)));
 
     assertEquals(
-        new Structure<>(
-            new TestParent(
-                new Structure<>(new TestChild(new Int8((byte) 0x69), new Str("can"))),
-                new Array<>(List.of(new Str("has"), new Str("strings?"))))),
+        new TestParent(new TestChild((byte) 0x69, "can"), List.of("has", "strings?")),
         decoder.decode(ByteBuffer.wrap(data)));
   }
 
@@ -149,51 +129,45 @@
   void testSimpleStructure() {
     var data = new byte[] {0x60, 0x70};
 
-    record TestRecord(Int8 b1, Int8 b2) {}
+    record TestRecord(byte b1, byte b2) {}
 
     var decoder =
         Decoder.ofStructure(
             TestRecord.class,
-            Decoder.ofInt8().withByteOrder(LITTLE_ENDIAN),
-            Decoder.ofInt8().withByteOrder(LITTLE_ENDIAN));
+            Decoder.ofByte().withByteOrder(LITTLE_ENDIAN),
+            Decoder.ofByte().withByteOrder(LITTLE_ENDIAN));
 
-    assertEquals(
-        new Structure<>(new TestRecord(new Int8((byte) 0x60), new Int8((byte) 0x70))),
-        decoder.decode(ByteBuffer.wrap(data)));
+    assertEquals(new TestRecord((byte) 0x60, (byte) 0x70), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
   void testPaddedStructureRight() {
     var data = new byte[] {0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00};
 
-    record TestRecord(Int32 b1, Int8 b2) {}
+    record TestRecord(int b1, byte b2) {}
 
     var decoder =
         Decoder.ofStructure(
             TestRecord.class,
-            Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN),
-            Decoder.ofInt8().withByteOrder(LITTLE_ENDIAN));
+            Decoder.ofInt().withByteOrder(LITTLE_ENDIAN),
+            Decoder.ofByte().withByteOrder(LITTLE_ENDIAN));
 
-    assertEquals(
-        new Structure<>(new TestRecord(new Int32(0x60), new Int8((byte) 0x70))),
-        decoder.decode(ByteBuffer.wrap(data)));
+    assertEquals(new TestRecord(0x60, (byte) 0x70), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
   void testPaddedStructureLeft() {
     var data = new byte[] {0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00};
 
-    record TestRecord(Int8 b1, Int32 b2) {}
+    record TestRecord(byte b1, int b2) {}
 
     var decoder =
         Decoder.ofStructure(
             TestRecord.class,
-            Decoder.ofInt8().withByteOrder(LITTLE_ENDIAN),
-            Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN));
+            Decoder.ofByte().withByteOrder(LITTLE_ENDIAN),
+            Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
 
-    assertEquals(
-        new Structure<>(new TestRecord(new Int8((byte) 0x60), new Int32(0x70))),
-        decoder.decode(ByteBuffer.wrap(data)));
+    assertEquals(new TestRecord((byte) 0x60, 0x70), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
@@ -218,20 +192,17 @@
           0x00
         };
 
-    record TestRecord(Int32 b1, Int8 b2) {}
+    record TestRecord(int b1, byte b2) {}
 
     var decoder =
         Decoder.ofArray(
             Decoder.ofStructure(
                 TestRecord.class,
-                Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN),
-                Decoder.ofInt8().withByteOrder(LITTLE_ENDIAN)));
+                Decoder.ofInt().withByteOrder(LITTLE_ENDIAN),
+                Decoder.ofByte().withByteOrder(LITTLE_ENDIAN)));
 
     assertEquals(
-        new Array<>(
-            List.of(
-                new Structure<>(new TestRecord(new Int32(96), new Int8((byte) 0x70))),
-                new Structure<>(new TestRecord(new Int32(648), new Int8((byte) 0xf7))))),
+        List.of(new TestRecord(96, (byte) 0x70), new TestRecord(648, (byte) 0xf7)),
         decoder.decode(ByteBuffer.wrap(data)));
   }
 
@@ -239,15 +210,10 @@
   void testByteArray() {
     var data = new byte[] {0x04, 0x05, 0x06, 0x07};
 
-    var decoder = Decoder.ofArray(Decoder.ofInt8());
+    var decoder = Decoder.ofArray(Decoder.ofByte());
 
     assertEquals(
-        new Array<>(
-            List.of(
-                new Int8((byte) 0x04),
-                new Int8((byte) 0x05),
-                new Int8((byte) 0x06),
-                new Int8((byte) 0x07))),
+        List.of((byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07),
         decoder.decode(ByteBuffer.wrap(data)));
   }
 
@@ -255,10 +221,9 @@
   void testIntegerArray() {
     var data = new byte[] {0x04, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00};
 
-    var decoder = Decoder.ofArray(Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN));
+    var decoder = Decoder.ofArray(Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
 
-    assertEquals(
-        new Array<>(List.of(new Int32(4), new Int32(258))), decoder.decode(ByteBuffer.wrap(data)));
+    assertEquals(List.of(4, 258), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
@@ -266,13 +231,11 @@
     var data =
         new byte[] {0x61, 0x20, 0x6B, 0x65, 0x79, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x06};
 
-    record TestEntry(Str key, Int32 value) {}
+    record TestEntry(String key, int value) {}
 
     var decoder =
         Decoder.ofStructure(
-            TestEntry.class, Decoder.ofStr(UTF_8), Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN));
-    assertEquals(
-        new Structure<>(new TestEntry(new Str("a key"), new Int32(514))),
-        decoder.decode(ByteBuffer.wrap(data)));
+            TestEntry.class, Decoder.ofString(UTF_8), Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
+    assertEquals(new TestEntry("a key", 514), decoder.decode(ByteBuffer.wrap(data)));
   }
 }