Repository: goccy/go-yaml
Pull request #671 (2025-02-23 08:18:59)

Add AutoInt option
Closes https://github.com/goccy/go-yaml/issues/427. Some YAML libraries automatically convert float values to integers when possible. However, this is not currently supported in goccy/go-yaml. While users can work around this issue by implementing custom marshalers, it would be more convenient to provide an option that enables this behavior.

Thank you for your review!

- [x] Describe the purpose for which you created this PR.  
- [x] Create test code that corresponds to the modification

codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/goccy/go-yaml/pull/671?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 77.63%. Comparing base [(`abc7083`)](https://app.codecov.io/gh/goccy/go-yaml/commit/abc70836f5a5623a92cf51d4bf40cbaf8fed2faa?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`3d500f7`)](https://app.codecov.io/gh/goccy/go-yaml/commit/3d500f7d03a7559d961a750c2bfe2e585ab30fdc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima).

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #671      +/-   ##
==========================================
+ Coverage   77.61%   77.63%   +0.01%     
==========================================
  Files          22       22              
  Lines        7953     7959       +6     
==========================================
+ Hits         6173     6179       +6     
  Misses       1362     1362              
  Partials      418      418              
```

</details>

Patch:
diff --git a/encode.go b/encode.go
index cc2961f..0a83017 100644
--- a/encode.go
+++ b/encode.go
@@ -38,6 +38,7 @@ type Encoder struct {
 	anchorNameMap              map[string]struct{}
 	anchorCallback             func(*ast.AnchorNode, interface{}) error
 	customMarshalerMap         map[reflect.Type]func(interface{}) ([]byte, error)
+	autoInt                    bool
 	useLiteralStyleIfMultiline bool
 	commentMap                 map[*Path][]*Comment
 	written                    bool
@@ -547,6 +548,9 @@ func (e *Encoder) encodeFloat(v float64, bitSize int) ast.Node {
 	}
 	value := strconv.FormatFloat(v, 'g', -1, bitSize)
 	if !strings.Contains(value, ".") && !strings.Contains(value, "e") {
+		if e.autoInt {
+			return ast.Integer(token.New(value, value, e.pos(e.column)))
+		}
 		// append x.0 suffix to keep float value context
 		value = fmt.Sprintf("%s.0", value)
 	}
diff --git a/option.go b/option.go
index a3be217..7dcd2ba 100644
--- a/option.go
+++ b/option.go
@@ -206,6 +206,15 @@ func CustomMarshaler[T any](marshaler func(T) ([]byte, error)) EncodeOption {
 	}
 }
 
+// AutoInt automatically converts floating-point numbers to integers when the fractional part is zero.
+// For example, a value of 1.0 will be encoded as 1.
+func AutoInt() EncodeOption {
+	return func(e *Encoder) error {
+		e.autoInt = true
+		return nil
+	}
+}
+
 // CommentPosition type of the position for comment.
 type CommentPosition int
 


Test patch:
diff --git a/encode_test.go b/encode_test.go
index be377c4..ef507c9 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -1283,6 +1283,54 @@ func TestEncoder_CustomMarshaler(t *testing.T) {
 	})
 }
 
+func TestEncoder_AutoInt(t *testing.T) {
+	for _, test := range []struct {
+		desc     string
+		input    any
+		expected string
+	}{
+		{
+			desc: "int-convertible float64",
+			input: map[string]float64{
+				"key": 1.0,
+			},
+			expected: "key: 1\n",
+		},
+		{
+			desc: "non int-convertible float64",
+			input: map[string]float64{
+				"key": 1.1,
+			},
+			expected: "key: 1.1\n",
+		},
+		{
+			desc: "int-convertible float32",
+			input: map[string]float32{
+				"key": 1.0,
+			},
+			expected: "key: 1\n",
+		},
+		{
+			desc: "non int-convertible float32",
+			input: map[string]float32{
+				"key": 1.1,
+			},
+			expected: "key: 1.1\n",
+		},
+	} {
+		t.Run(test.desc, func(t *testing.T) {
+			var buf bytes.Buffer
+			enc := yaml.NewEncoder(&buf, yaml.AutoInt())
+			if err := enc.Encode(test.input); err != nil {
+				t.Fatalf("failed to encode: %s", err)
+			}
+			if actual := buf.String(); actual != test.expected {
+				t.Errorf("expect:\n%s\nactual\n%s\n", test.expected, actual)
+			}
+		})
+	}
+}
+
 func TestEncoder_MultipleDocuments(t *testing.T) {
 	var buf bytes.Buffer
 	enc := yaml.NewEncoder(&buf)