Add Decoder#ofDictionaryEntry.

Change-Id: Ie78096e1a7cfd3bcfa446e3ababe15b910d0c23c
diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
index 78e4bdd..6a4d01d 100644
--- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
+++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
@@ -92,7 +92,17 @@
       case 'v' -> Decoder.ofVariant();
       case 'm' -> Decoder.ofMaybe(parseSignature(signature));
       case 'a' -> Decoder.ofArray(parseSignature(signature));
-      case '(', '{' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
+      case '(' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
+      case '{' -> {
+        var tupleTypes = parseTupleTypes(signature);
+        if (tupleTypes.size() != 2) {
+          throw new ParseException(
+              String.format(
+                  "dictionary entry type with %d components, expected 2", tupleTypes.size()),
+              signature.position());
+        }
+        yield Decoder.ofDictionaryEntry(tupleTypes.get(0), tupleTypes.get(1));
+      }
       default -> throw new ParseException(
           String.format("encountered unknown signature byte '%c'", c), signature.position());
     };