Skip to content

Commit

Permalink
Merge pull request #26 from therapon/master
Browse files Browse the repository at this point in the history
Merge of fernomac/ion-go
  • Loading branch information
therapon committed May 20, 2020
2 parents 2bb6136 + 0cddde4 commit e32dee6
Show file tree
Hide file tree
Showing 77 changed files with 13,790 additions and 8,010 deletions.
8 changes: 8 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@


Issue #, if available:

Description of changes:

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

11 changes: 9 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,12 @@ jobs:
- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
- name: Test with coverage
run: go test -v ./... -coverprofile coverage.txt


- name: Upload Coverage report to CodeCov
uses: codecov/codecov-action@v1
with:
token: ${{secrets.CODECOV_TOKEN}}
file: ./coverage.txt
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.

(https://github.com/fernomac/ion-go/blob/master/NOTICE)
Amazon Ion Go
Copyright 2019 David Murray
244 changes: 236 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,250 @@ $ goimports -w .

It is recommended that you hook this in your favorite IDE (`Tools` > `File Watchers` in Goland, for example).

## Usage

Import `github.com/amzn/ion-go/ion` and you're off to the races.

### Marshaling and Unmarshaling

Similar to GoLang's built-in [json](https://golang.org/pkg/encoding/json/) package,
you can marshal and unmarshal Go types to Ion. Marshaling requires you to specify
whether you'd like text or binary Ion. Unmarshaling is smart enough to do the right
thing. Both respect json name tags, and `Marshal` honors `omitempty`.

```Go
type T struct {
A string
B struct {
RenamedC int `json:"C"`
D []int `json:",omitempty"`
}
}

func main() {
t := T{}

err := ion.Unmarshal([]byte(`{A:"Ion!",B:{C:2,D:[3,4]}}`), &t)
if err != nil {
panic(err)
}
fmt.Printf("--- t:\n%v\n\n", t)

text, err := ion.MarshalText(&t)
if err != nil {
panic(err)
}
fmt.Printf("--- text:\n%s\n\n", string(text))

binary, err := ion.MarshalBinary(&t)
if err != nil {
panic(err)
}
fmt.Printf("--- binary:\n%X\n\n", binary)
}
```

### Encoding and Decoding

To read or write multiple values at once, use an `Encoder` or `Decoder`:

```Go
func main() {
dec := ion.NewTextDecoder(os.Stdin)
enc := ion.NewBinaryEncoder(os.Stdout)

for {
// Decode one Ion whole value from stdin.
val, err := dec.Decode()
if err == ion.ErrNoInput {
break
} else if err != nil {
panic(err)
}

// Encode it to stdout.
if err := enc.Encode(val); err != nil {
panic(err)
}
}

if err := enc.Finish(); err != nil {
panic(err)
}
}
```

### Reading and Writing

For low-level streaming read and write access, use a `Reader` or `Writer`.

```Go
func copy(in ion.Reader, out ion.Writer) {
for in.Next() {
name := in.FieldName()
if name != "" {
out.FieldName(name)
}

annos := in.Annotations()
if len(annos) > 0 {
out.Annotations(annos...)
}

switch in.Type() {
case ion.BoolType:
val, err := in.BoolValue()
if err != nil {
panic(err)
}
out.WriteBool(val)

case ion.IntType:
val, err := in.Int64Value()
if err != nil {
panic(err)
}
out.WriteInt(val)

case ion.StringType:
val, err := in.StringValue()
if err != nil {
panic(err)
}
out.WriteString(val)

case ion.ListType:
in.StepIn()
out.BeginList()
copy(in, out)
in.StepOut()
out.EndList()

case ion.StructType:
in.StepIn()
out.BeginStruct()
copy(in, out)
in.StepOut()
out.EndStruct()
}
}

if in.Err() != nil {
panic(in.Err())
}
}

func main() {
in := ion.NewReader(os.Stdin)
out := ion.NewBinaryWriter(os.Stdout)

copy(in, out)

if err := out.Finish(); err != nil {
panic(err)
}
}
```

### Symbol Tables

By default, when writing binary Ion, a local symbol table is built as you write
values (which are buffered in memory until you call `Finish` so the symbol table
can be written out first). You can optionally provide one or more
`SharedSymbolTable`s to the writer, which it will reference as needed rather
than directly including those symbols in the local symbol table.

```Go
type Item struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}

var ItemSharedSymbols = ion.NewSharedSymbolTable("item", 1, []string{
"item",
"id",
"name",
"description",
})

type SpicyItem struct {
Item
Spiciness int `json:"spiciness"`
}

func WriteSpicyItemsTo(out io.Writer, items []SpicyItem) error {
writer := ion.NewBinaryWriter(out, ItemSharedSymbols)

for _, item := range items {
writer.Annotation("item")
if err := ion.EncodeTo(writer, item); err != nil {
return err
}
}

return writer.Finish()
}
```

You can alternatively provide the writer with a complete, pre-built local symbol table.
This allows values to be written without buffering, however any attempt to write a
symbol that is not included in the symbol table will result in an error:

```Go
func WriteItemsToLST(out io.Writer, items []SpicyItem) error {
lst := ion.NewLocalSymbolTable([]SharedSymbolTable{ItemSharedSymbols}, []string{
"spiciness",
})

writer := ion.NewBinaryWriterLST(out, lst)

for _, item := range items {
writer.Annotation("item")
if err := ion.EncodeTo(writer, item); err != nil {
return err
}
}

return writer.Finish()
}
```

When reading binary Ion, shared symbol tables are provided by a `Catalog`. A basic
catalog can be constructed by calling `NewCatalog`; a smarter implementation may
load shared symbol tables from a database on demand.

```Go

func ReadItemsFrom(in io.Reader) ([]Item, error) {
item := Item{}
items := []Item{}

cat := ion.NewCatalog(ItemSharedSymbols)
dec := ion.NewDecoder(ion.NewReaderCat(in, cat))

for {
err := dec.DecodeTo(&item)
if err == ion.ErrNoInput {
return items, nil
}
if err != nil {
return nil, err
}

items = append(items, item)
}
}
```
## Notes

* This package only supports text as UTF-8. It does not support the UTF-16 or UTF-32 forms.
* Only a text and binary parsers, and a sketch of the types have been implemented so far.
* The `Float` type is limited to the minimum and maximum values that Go is able to handle with float64. These values
are approximately `1.797693e+308` and `4.940656e-324`. Values that are too large will round to infinity and
values that are too small will round to zero.
* Textual representation of `Timestamp` currently stops at microsecond precision.

## TODO

* Symbol table construction and verification.
* Define the external interfaces for marshalling and unmarshalling.
* Serializing Values to the textual and binary forms.
Expand All @@ -78,10 +312,4 @@ It is recommended that you hook this in your favorite IDE (`Tools` > `File Watch
* Make the `Timestamp` type handle the case of unknown local offsets.
* Make the `Float` and `Decimal` types recognize negative zero.

## Usage

```go
package main

// TODO
```
6 changes: 0 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
module github.com/amzn/ion-go

go 1.13

require (
github.com/google/go-cmp v0.4.0
github.com/pkg/errors v0.9.1
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114
)
Empty file added internal/.keep
Empty file.
Loading

0 comments on commit e32dee6

Please sign in to comment.