amazon-ion / ion-go

A Go implementation of Amazon Ion.
https://amazon-ion.github.io/ion-docs/
Apache License 2.0
175 stars 31 forks source link

Decode multiple ion structs in go #195

Closed h3n4l closed 3 months ago

h3n4l commented 6 months ago

I have a file which content is:

{
    name: "call empty",
    statement: "foobar()",
    assert: [
        {
            result: SyntaxSuccess
        },
    ]
}

{
    name: "call one argument",
    statement: "foobar(1)",
    assert: [
        {
            result: SyntaxSuccess
        },
    ]
}

{
    name: "call two arguments",
    statement: "foobar(1, 2)",
    assert: [
        {
            result: SyntaxSuccess
        },
    ]
}

{
    name: "call with multiple",
    statement: "foobar(1, 2, 3)",
    assert: [
        {
            result: SyntaxSuccess
        },
    ]
}

{
    name: "call with case insensitive function name",
    statement: "mY_fUnCtIoN(a)",
    assert: [
        {
            result: SyntaxSuccess
        },
    ]
}

'substring'::[
    'sql92'::[
        {
            name: "call SUBSTRING sql92 syntax",
            statement: "substring('test' from 100)",
            assert: [
                {
                    result: SyntaxSuccess
                },
            ]
        },
        {
            name: "call SUBSTRING sql92 syntax with length",
            statement: "substring('test' from 100 for 50)",
            assert: [
                {
                    result: SyntaxSuccess
                },
            ]
        },
    ],
    'partiql'::[
        {
            name: "call SUBSTRING function argument list syntax",
            statement: "substring('test', 100)",
            assert: [
                {
                    result: SyntaxSuccess
                },
            ]
        },
        {
            name: "call SUBSTRING function argument list syntax with length",
            statement: "substring('test', 100, 50)",
            assert: [
                {
                    result: SyntaxSuccess
                },
            ]
        },
    ]
]

'trim'::[
    {
        name: "call TRIM single argument",
        statement: "trim('test')",
        assert: [
            {
                result: SyntaxSuccess
            },
        ]
    },
    {
        name: "call TRIM two arguments default specification",
        statement: "trim(' ' from 'test')",
        assert: [
            {
                result: SyntaxSuccess
            },
        ]
    },
    {
        name: "call TRIM two arguments using BOTH",
        statement: "trim(both from 'test')",
        assert: [
            {
                result: SyntaxSuccess
            },
        ]
    },
    {
        name: "call TRIM two arguments using LEADING",
        statement: "trim(leading from 'test')",
        assert: [
            {
                result: SyntaxSuccess
            },
        ]
    },
    {
        name: "call TRIM two arguments using TRAILING",
        statement: "trim(trailing from 'test')",
        assert: [
            {
                result: SyntaxSuccess
            },
        ]
    },
]

'position'::[
    {
        name: "call POSITION",
        statement: "POSITION('abc' IN 'abcdefg')",
        assert: [
            {
                result: SyntaxSuccess
            },
        ]
    },
]

How can I decode them into go struct? I think the documentation and spec are not clear about it.

nirosys commented 5 months ago

Hello h3n4l! Sorry for the delayed response.

In general, you can use ion.UnmarshalFrom(ion.Reader, interface{}) error to deserialize into a struct. However you need to know what kind of struct it would deserialize into ahead of time.

If you're looking to decode the entire file into a single struct, and you don't know the exact organization of the file, then you'd definitely want to look at the Unmarshal interface. With an Unmarshal implementation you'd need to walk the values in the ion data, and Unmarshal the values conditionally based on what type of data, or what annotations they have.

Unfortunately, the way the Decoder works at the moment, it will call Next before deserializing a value. So it makes leaning on other Unmarshal implementations a little more difficult.

You'd need to define the struct that you want to put the data into:

type CallDetails struct {
 // ...
}

Then add the UnmarshalIon func to it,

func (c *CallDetails) UnmarshalIon(r ion.Reader) error {
   annotations, err := r.Annotations() // You can use the annotations to help determine your deserialization.
   // ..
   switch r.Type() {
     case ion.StructType:
        // Handle struct values
     case ion.ListType:
        // Handle list values
   } 
   return err
}

In this func you can use the reader to access data in the stream and populate the struct. Very similar to Go's encoding/json Unmarshaller but without a json.RawMessage equivalent.

With this, you can create a reader, and decoder, to decode the values like so:

reader := ion.NewReader()
decoder := ion.NewDecoder(reader)

var value CallDetails
err := decoder.DecodeTo(&value)
if err != nil && err != ion.ErrNoInput {
   panic(err)   
}
// Do things with value

Let me know if you still have questions, I can also help put a more concrete example together if you let me know how you want the data to look once deserialized.

nirosys commented 3 months ago

Closing this issue, feel free to open another if you have any other questions. :)