Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question / Documentation: Is it possible to modify a value in an existing document without changing the rest of the document ? #574

Open
masenocturnal opened this issue Dec 6, 2024 · 4 comments
Labels
question Further information is requested

Comments

@masenocturnal
Copy link

masenocturnal commented Dec 6, 2024

Is your feature request related to a problem? Please describe.

I have an existing YAML document below.

q:
  r: "abc"
  s: "defg"

a: 
  b: "hijk"
  c: "lmnop"

My goal is to modify the value of 'c' only, leaving the rest of the document as is.

I have tried the following and while the value is updated, the process of writing out the document modifies the order of the document , removes blank lines and other formatting which has been added.

Is it at all possible to preserve the original document and only change the values ? If so, would it be possible to offer some guidance as to the high level approach ?

I apologise if this has been answered elsewhere or should be obvious.

Thank you for your excellent library.



 func main() {
    err := example("a","test", "/tmp/example.yaml")
 }
 
func example(serviceName string, imageTag string, filePath string) error {

	yamlContent, err := os.ReadFile(filePath)
		if err != nil {
		return err
	}

	var data map[string]interface{}
	err = yaml.UnmarshalWithOptions([]byte(yamlContent), &data)
	if err != nil {
		return err
	}

	if key, ok := data["a"].(map[string]interface{}); ok {
		key["c"] = imageTag

	} else {
		return fmt.Errorf("error: %s not found in YAML structure", "a")
	}

         updatedData, err := yaml.Marshal(data)
	if err != nil {
		return err
	}

	if err := os.WriteFile(filePath, updatedData, 0644); err != nil {
		return err
	}

	return nil

}

Describe alternatives you've considered
I've tried using the MarshalWithOptions and passing the the indent function but that only (as expected) addresses indentation.

Additional context
Add any other context or screenshots about the feature request here.

@goccy goccy added question Further information is requested and removed feature request labels Dec 6, 2024
@goccy
Copy link
Owner

goccy commented Dec 7, 2024

@masenocturnal This is example code

https://go.dev/play/p/QdwCh_tU-XR

package main

import (
	"fmt"
	"strings"

	"github.com/goccy/go-yaml"
	"github.com/goccy/go-yaml/parser"
)

func main() {
	data := `
q:
  r: "abc"
  s: "defg"

a: 
  b: "hijk"
  c: "lmnop"
`
	f, err := parser.ParseBytes([]byte(data), parser.ParseComments)
	if err != nil {
		panic(err)
	}
	path, err := yaml.PathString("$.a.c")
	if err != nil {
		panic(err)
	}
	if err := path.ReplaceWithReader(f, strings.NewReader("foo")); err != nil {
		panic(err)
	}
	fmt.Println(f)
}

@masenocturnal
Copy link
Author

Thank you so much for taking the time to educate me. This preserves the whole document including the comments and the spacing and the order! This is wonderful and it's great that you can do this with your library.

In the example you provided the value of 'c' is not quoted as it is in the source.
Is it possible to instruct the encoder to add quotes around strings ?

output

am in ~/tmp/foo λ ./main 
q:
  r: "abc"
  s: "defg"

a:
  b: "hijk"
  c: foo

I tried creating a custom type and adding an custom marhsaler however it then appears to escape it ( i.e """)

I suspect other people may also have this question / desire to modify part of a file.

I'm happy to PR this either into the examples on the main page if that is appropriate place.
Alternatively I can add it to an examples folder or another more appropriate place if you think that would be helpful (e.g start a folder of examples ? )

@masenocturnal
Copy link
Author

I've done a bit of digging with the debugger and adding some quotes around the %s in the toString function in ast.go seems to result in the desired output.

fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())

to

fmt.Sprintf("%s%s: \"%s\"", space, n.Key.String(), n.Value.String())

image

Am I on the right track ?

@goccy
Copy link
Owner

goccy commented Dec 9, 2024

No, that is not where the fix should be made.

If you need double-quote text, you can select two options.

  1. Use double-quoted text directly
  2. Use ast.StringNode with DoubleQuote token

1. Use double-quoted text directly

Use strconv.Quote to text https://go.dev/play/p/tN7bjeEaouX

2. Use ast.StringNode with DoubleQuote token

Since ast.Node implements io.Reader, you can use it as an argument of path.ReplaceWithReader

ast.String(token.DoubleQuote("foo", "foo", nil))

https://go.dev/play/p/h_FNGPWzEIY

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants