AlecAivazis / survey

A golang library for building interactive and accessible prompts with full support for windows and posix terminals.
MIT License
4.07k stars 349 forks source link

"Unmarshall" into struct and conditional questioning #454

Closed williamokano-dh closed 1 year ago

williamokano-dh commented 1 year ago

Hey, I was playing around with your lib and I got some questions, I'll give some context as well.

First of all, I'm a beginner at golang, so if I say or ask something dumb, forgive me.

  1. Is it possible to the "Unmarshalling" of the questions into nested objects? I noticed that is it possible to tag the property very similar on how to json tagging works, but I tried the following and it didn't work for me.
type AdditionalInfo struct {
  Age int `survey:"age"`
}

type Person struct {
  Name string
  AdditionalInfo AdditionalInfo
}

qs := []*survey.Question{
  {
    Name: "name",
    Prompt: &survey.Input{Message: "Your name"},
  },
  {
    Name: "age",
    Prompt: &survey.Input{Message: "Your age"},
  }
}

_ = survey.Ask(qs, &Person{})

But it didn't work. The age is "unmarshalled" as 0, most likely since it's the default.

  1. Is it possible to have conditional questioning, for example, have some questions depending on the result of the previous one?

The whole idea here would be to create some flows with just 1 usage of question. If not, that's fine, I can always do several calls with specific objects :)

E.g.: I want to inquiry drivers licence number only if the age of the person if higher than 16.

qs := []*survey.Question{
        {
            Name: "age",
            Prompt: &survey.Input{Message: "Name"},
        },
        {
            Name: "age",
            Prompt: &survey.Input{Message: "Age"},
        },
        {
            Name: "drivers_licence_number",
            Prompt: &survey.Input{Message: "Driver's licence numbers"},
            Condition: "Question.Age > 16",
        },
    }

Thanks a lot

mislav commented 1 year ago

The whole idea here would be to create some flows with just 1 usage of question. If not, that's fine, I can always do several calls with specific objects :)

Hi, as you have guessed, the best way to accomplish this is to make several survey.Ask() invocations and in between them handle your application's logic to modify questions based on previous responses. Survey has no built-in support for this.

  1. Is it possible to the "Unmarshalling" of the questions into nested objects? I noticed that is it possible to tag the property very similar on how to json tagging works

No, unlike Go json package, Survey does not recognize struct field tags (i.e. the `survey:"age"` bit in your example). Instead, this survey question:

        {
            Name: "age",
            Prompt: &survey.Input{Message: "Age"},
        },

will save the answer in the struct field literally named Age. The first character of the field name is now uppercase because the field needs to be "public" (in Go terms) in order to be writeable from libraries like Survey.

However, the struct field must be string and not int. All answers to Survey questions are technically strings, even if they just consist of numbers. The only exception is the Select prompt which can optionally return the index of the selected option, and thus save the answer into an int receiver.

After the user has entered their age, you can convert their string answer to an integer you can use in Go like so:

person := struct {
  Name string
  Age string
}{}
err := survey.Ask(qs, &person)
// (handle error)
personAge, err := strconv.Atoi(person.Age)
// (handle error)

Good luck!