Repository: segmentio/parquet-go (go)

Issue:
How to make an optional map?
Take the sample below:
```go
type Spans struct {
  Attributes map[string]string
}

s := parquet.SchemaOf(new(RowType))
fmt.Println(s)
```

It prints:
```
message Spans {
  required group Attributes (MAP) {
    repeated group key_value {
      required binary key (STRING);
      required binary value (STRING);
    }
  }
}
```

However, I want the Attributes map to be optional:
```
message Spans {
  optional group Attributes (MAP) {
    repeated group key_value {
      required binary key (STRING);
      required binary value (STRING);
    }
  }
}
```

I try to add a tag, but it makes the value optional, not the map.
```
type Spans struct {
	Attributes map[string]string  `parquet:"Attributes,optional"`
}
```



Could you please teach me how to do it?

Pull request description:
Add the parquet-value tag and add the ability to make a map optional
Fixes #280 

Patch:
diff --git a/schema.go b/schema.go
index 6026317..68e5e28 100644
--- a/schema.go
+++ b/schema.go
@@ -82,8 +82,11 @@ type Schema struct {
 // and the data will not be written into the parquet file(s).
 // Note that a field with name "-" can still be generated using the tag "-,".
 //
-// Map key configuration can be pass using the `parquet-key` tag. This tag will follow the
-// same properties than the `parquet` tag.
+// The configuration of Parquet maps are done via two tags:
+//   - The `parquet-key` tag allows to configure the key of a map.
+//   - The parquet-value tag allows users to configure a map's values, for example to declare their native Parquet types.
+//
+// When configuring a Parquet map, the `parquet` tag will configure the map itself.
 //
 // For example, the following will set the int64 key of the map to be a timestamp:
 //
@@ -315,6 +318,7 @@ func structNodeOf(t reflect.Type) *structNode {
 		field.Node = makeNodeOf(fields[i].Type, fields[i].Name, []string{
 			fields[i].Tag.Get("parquet"),
 			fields[i].Tag.Get("parquet-key"),
+			fields[i].Tag.Get("parquet-value"),
 		})
 		s.fields[i] = field
 	}
@@ -521,18 +525,33 @@ func nodeOf(t reflect.Type, tag []string) Node {
 		}
 
 	case reflect.Map:
-		var valueTag, keyTag string
+		var mapTag, valueTag, keyTag string
 		if len(tag) > 0 {
-			valueTag = tag[0]
+			mapTag = tag[0]
 			if len(tag) > 1 {
 				keyTag = tag[1]
 			}
+			if len(tag) >= 2 {
+				valueTag = tag[2]
+			}
 		}
+
 		n = Map(
 			makeNodeOf(t.Key(), t.Name(), []string{keyTag}),
 			makeNodeOf(t.Elem(), t.Name(), []string{valueTag}),
 		)
 
+		forEachTagOption([]string{mapTag}, func(option, args string) {
+			switch option {
+			case "":
+				return
+			case "optional":
+				n = Optional(n)
+			default:
+				throwUnknownTag(t, "map", option)
+			}
+		})
+
 	case reflect.Struct:
 		return structNodeOf(t)
 	}


Test patch:
diff --git a/schema_test.go b/schema_test.go
index 333854f..f84772a 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -137,6 +137,48 @@ func TestSchemaOf(t *testing.T) {
 			required binary value (STRING);
 		}
 	}
+}`,
+		},
+
+		{
+			value: new(struct {
+				A map[int64]string `parquet:",optional" parquet-value:",json"`
+			}),
+			print: `message {
+	optional group A (MAP) {
+		repeated group key_value {
+			required int64 key (INT(64,true));
+			required binary value (JSON);
+		}
+	}
+}`,
+		},
+
+		{
+			value: new(struct {
+				A map[int64]string `parquet:",optional"`
+			}),
+			print: `message {
+	optional group A (MAP) {
+		repeated group key_value {
+			required int64 key (INT(64,true));
+			required binary value (STRING);
+		}
+	}
+}`,
+		},
+
+		{
+			value: new(struct {
+				A map[int64]string `parquet:",optional" parquet-value:",json" parquet-key:",timestamp(microsecond)"`
+			}),
+			print: `message {
+	optional group A (MAP) {
+		repeated group key_value {
+			required int64 key (TIMESTAMP(isAdjustedToUTC=true,unit=MICROS));
+			required binary value (JSON);
+		}
+	}
 }`,
 		},
 	}