Repository: amzn/ion-go
Pull request #165 (2020-10-02 19:37:50)

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


codecov-commenter: # [Codecov](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=h1) Report
> Merging [#165](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=desc) into [master](https://codecov.io/gh/amzn/ion-go/commit/0d00cfc6461e91b623c18ef3c2e4887911dd6573?el=desc) will **decrease** coverage by `0.12%`.
> The diff coverage is `55.88%`.

[![Impacted file tree graph](https://codecov.io/gh/amzn/ion-go/pull/165/graphs/tree.svg?width=650&height=150&src=pr&token=OBDT1TNoNE)](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #165      +/-   ##
==========================================
- Coverage   74.90%   74.78%   -0.13%     
==========================================
  Files          25       25              
  Lines        4857     4893      +36     
==========================================
+ Hits         3638     3659      +21     
- Misses        724      734      +10     
- Partials      495      500       +5     
```


| [Impacted Files](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [ion/symboltable.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3N5bWJvbHRhYmxlLmdv) | `56.29% <21.05%> (-3.18%)` | :arrow_down: |
| [ion/marshal.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL21hcnNoYWwuZ28=) | `69.39% <66.66%> (ø)` | |
| [ion/binarywriter.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL2JpbmFyeXdyaXRlci5nbw==) | `76.00% <68.75%> (-0.08%)` | :arrow_down: |
| [ion/textwriter.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3RleHR3cml0ZXIuZ28=) | `61.45% <78.94%> (+2.25%)` | :arrow_up: |
| [ion/reader.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3JlYWRlci5nbw==) | `65.00% <100.00%> (-0.35%)` | :arrow_down: |
| [ion/symboltoken.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3N5bWJvbHRva2VuLmdv) | `92.45% <100.00%> (+1.34%)` | :arrow_up: |
| [ion/writer.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3dyaXRlci5nbw==) | `66.66% <100.00%> (ø)` | |
| [ion/bitstream.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL2JpdHN0cmVhbS5nbw==) | `79.46% <0.00%> (-0.39%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=footer). Last update [0d00cfc...2c8bed2](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov-io: # [Codecov](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=h1) Report
> Merging [#165](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=desc) into [master](https://codecov.io/gh/amzn/ion-go/commit/0d00cfc6461e91b623c18ef3c2e4887911dd6573?el=desc) will **decrease** coverage by `0.38%`.
> The diff coverage is `51.72%`.

[![Impacted file tree graph](https://codecov.io/gh/amzn/ion-go/pull/165/graphs/tree.svg?width=650&height=150&src=pr&token=OBDT1TNoNE)](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #165      +/-   ##
==========================================
- Coverage   74.90%   74.51%   -0.39%     
==========================================
  Files          25       25              
  Lines        4857     4901      +44     
==========================================
+ Hits         3638     3652      +14     
- Misses        724      742      +18     
- Partials      495      507      +12     
```


| [Impacted Files](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [ion/unmarshal.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3VubWFyc2hhbC5nbw==) | `67.18% <0.00%> (-0.90%)` | :arrow_down: |
| [ion/writer.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3dyaXRlci5nbw==) | `66.66% <ø> (ø)` | |
| [ion/symboltable.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3N5bWJvbHRhYmxlLmdv) | `56.29% <21.73%> (-3.18%)` | :arrow_down: |
| [ion/readlocalsymboltable.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3JlYWRsb2NhbHN5bWJvbHRhYmxlLmdv) | `66.93% <50.00%> (-1.87%)` | :arrow_down: |
| [ion/skipper.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3NraXBwZXIuZ28=) | `60.18% <57.14%> (-0.19%)` | :arrow_down: |
| [ion/textwriter.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3RleHR3cml0ZXIuZ28=) | `60.00% <65.38%> (+0.79%)` | :arrow_up: |
| [ion/marshal.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL21hcnNoYWwuZ28=) | `69.39% <66.66%> (ø)` | |
| [ion/binarywriter.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL2JpbmFyeXdyaXRlci5nbw==) | `76.00% <68.75%> (-0.08%)` | :arrow_down: |
| [ion/symboltoken.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL3N5bWJvbHRva2VuLmdv) | `90.38% <85.71%> (-0.73%)` | :arrow_down: |
| [ion/decimal.go](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree#diff-aW9uL2RlY2ltYWwuZ28=) | `75.16% <100.00%> (-0.33%)` | :arrow_down: |
| ... and [3 more](https://codecov.io/gh/amzn/ion-go/pull/165/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=footer). Last update [0d00cfc...d3a3971](https://codecov.io/gh/amzn/ion-go/pull/165?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cmd/ion-go/eventwriter.go b/cmd/ion-go/eventwriter.go
index 9bc6c05..6361246 100644
--- a/cmd/ion-go/eventwriter.go
+++ b/cmd/ion-go/eventwriter.go
@@ -37,12 +37,12 @@ type eventwriter struct {
 // of ion-test-driver events.
 func NewEventWriter(out io.Writer) ion.Writer {
 	w := ion.NewTextWriter(out)
-	w.WriteSymbol("$ion_event_stream")
+	w.WriteSymbol(ion.NewSymbolTokenFromString("$ion_event_stream"))
 
 	return &eventwriter{enc: ion.NewEncoder(w)}
 }
 
-func (e *eventwriter) FieldNameSymbol(val ion.SymbolToken) error {
+func (e *eventwriter) FieldName(val ion.SymbolToken) error {
 	if val.Text == nil {
 		sid := fmt.Sprintf("$%v", val.LocalSID)
 		e.fieldname = &sid
@@ -53,11 +53,6 @@ func (e *eventwriter) FieldNameSymbol(val ion.SymbolToken) error {
 	return nil
 }
 
-func (e *eventwriter) FieldName(val string) error {
-	e.fieldname = &val
-	return nil
-}
-
 func (e *eventwriter) Annotation(val ion.SymbolToken) error {
 	e.annotations = append(e.annotations, val)
 	return nil
@@ -140,7 +135,7 @@ func (e *eventwriter) WriteTimestamp(val ion.Timestamp) error {
 	})
 }
 
-func (e *eventwriter) WriteSymbol(val string) error {
+func (e *eventwriter) WriteSymbol(val ion.SymbolToken) error {
 	return e.write(event{
 		EventType: scalar,
 		IonType:   iontype(ion.SymbolType),
@@ -148,6 +143,14 @@ func (e *eventwriter) WriteSymbol(val string) error {
 	})
 }
 
+func (e *eventwriter) WriteSymbolFromString(val string) error {
+	return e.write(event{
+		EventType: scalar,
+		IonType:   iontype(ion.SymbolType),
+		ValueText: stringify(val),
+	})
+}
+
 func (e *eventwriter) WriteString(val string) error {
 	return e.write(event{
 		EventType: scalar,
@@ -253,7 +256,7 @@ func stringify(val interface{}) string {
 	return string(bs)
 }
 
-func symbolify(val string) string {
+func symbolify(val ion.SymbolToken) string {
 	buf := strings.Builder{}
 	w := ion.NewTextWriterOpts(&buf, ion.TextWriterQuietFinish)
 
diff --git a/cmd/ion-go/main.go b/cmd/ion-go/main.go
index 9491014..8f349b4 100644
--- a/cmd/ion-go/main.go
+++ b/cmd/ion-go/main.go
@@ -78,28 +78,24 @@ func printVersion() error {
 		return err
 	}
 	{
-		if err := w.FieldName("version"); err != nil {
+		if err := w.FieldName(ion.NewSymbolTokenFromString("version")); err != nil {
 			return err
 		}
 		if err := w.WriteString(internal.GitCommit); err != nil {
 			return err
 		}
 
+		if err := w.FieldName(ion.NewSymbolTokenFromString("build_time")); err != nil {
+			return err
+		}
+
 		buildtime, err := ion.NewTimestampFromStr(internal.BuildTime, ion.TimestampPrecisionSecond, ion.TimezoneUTC)
 		if err == nil {
-			if err := w.FieldName("build_time"); err != nil {
-				return err
-			}
 			if err := w.WriteTimestamp(buildtime); err != nil {
 				return err
 			}
-		} else {
-			if err := w.FieldName("build_time"); err != nil {
-				return err
-			}
-			if err := w.WriteString("unknown-buildtime"); err != nil {
-				return err
-			}
+		} else if err := w.WriteString("unknown-buildtime"); err != nil {
+			return err
 		}
 	}
 	if err := w.EndStruct(); err != nil {
diff --git a/cmd/ion-go/nopwriter.go b/cmd/ion-go/nopwriter.go
index 2eea05b..97e880e 100644
--- a/cmd/ion-go/nopwriter.go
+++ b/cmd/ion-go/nopwriter.go
@@ -28,11 +28,7 @@ func NewNopWriter() ion.Writer {
 	return nopwriter{}
 }
 
-func (nopwriter) FieldName(string) error {
-	return nil
-}
-
-func (nopwriter) FieldNameSymbol(val ion.SymbolToken) error {
+func (nopwriter) FieldName(ion.SymbolToken) error {
 	return nil
 }
 
@@ -80,7 +76,11 @@ func (nopwriter) WriteTimestamp(ion.Timestamp) error {
 	return nil
 }
 
-func (nopwriter) WriteSymbol(string) error {
+func (nopwriter) WriteSymbol(ion.SymbolToken) error {
+	return nil
+}
+
+func (nopwriter) WriteSymbolFromString(string) error {
 	return nil
 }
 
diff --git a/cmd/ion-go/process.go b/cmd/ion-go/process.go
index b48741d..0f8dfdf 100644
--- a/cmd/ion-go/process.go
+++ b/cmd/ion-go/process.go
@@ -220,8 +220,8 @@ func (p *processor) process(in ion.Reader) error {
 		if e != nil {
 			return p.error(read, err)
 		}
-		if name != nil && name.Text != nil {
-			if err = p.out.FieldName(*name.Text); err != nil {
+		if name != nil {
+			if err = p.out.FieldName(*name); err != nil {
 				return p.error(write, err)
 			}
 		}
@@ -301,7 +301,7 @@ func (p *processor) process(in ion.Reader) error {
 			err = p.out.WriteTimestamp(*val)
 
 		case ion.SymbolType:
-			val, err := in.StringValue()
+			val, err := in.SymbolValue()
 			if err != nil {
 				return p.error(read, err)
 			}
diff --git a/cmd/ion-go/schema.go b/cmd/ion-go/schema.go
index 47083d8..8bcd4fe 100644
--- a/cmd/ion-go/schema.go
+++ b/cmd/ion-go/schema.go
@@ -56,13 +56,13 @@ func (e eventtype) String() string {
 }
 
 func (e eventtype) MarshalIon(w ion.Writer) error {
-	return w.WriteSymbol(e.String())
+	return w.WriteSymbolFromString(e.String())
 }
 
 type iontype ion.Type
 
 func (i iontype) MarshalIon(w ion.Writer) error {
-	return w.WriteSymbol(strings.ToUpper(ion.Type(i).String()))
+	return w.WriteSymbolFromString(strings.ToUpper(ion.Type(i).String()))
 }
 
 // event describes an Ion processing event.
@@ -99,7 +99,7 @@ func (e errortype) String() string {
 }
 
 func (e errortype) MarshalIon(w ion.Writer) error {
-	return w.WriteSymbol(e.String())
+	return w.WriteSymbolFromString(e.String())
 }
 
 // errordescription describes an error during Ion processing.
diff --git a/ion/binarywriter.go b/ion/binarywriter.go
index 99c2f2b..ec05895 100644
--- a/ion/binarywriter.go
+++ b/ion/binarywriter.go
@@ -21,8 +21,6 @@ import (
 	"io"
 	"math"
 	"math/big"
-	"strconv"
-	"strings"
 	"time"
 )
 
@@ -251,14 +249,36 @@ func (w *binaryWriter) WriteTimestamp(val Timestamp) error {
 	return w.writeValue("Writer.WriteTimestamp", buf)
 }
 
-// WriteSymbol writes a symbol value.
-func (w *binaryWriter) WriteSymbol(val string) error {
-	id, err := w.resolve("Writer.WriteSymbol", val)
-	if err != nil {
-		w.err = err
-		return err
+// WriteSymbol writes a symbol value given a SymbolToken.
+func (w *binaryWriter) WriteSymbol(val SymbolToken) error {
+	var id uint64
+	if val.LocalSID != SymbolIDUnknown {
+		id = uint64(val.LocalSID)
+	} else if val.Text != nil {
+		id, w.err = w.resolveFromSymbolTable("Writer.WriteSymbol", *val.Text)
+		if w.err != nil {
+			return w.err
+		}
+	} else {
+		return &UsageError{"Writer.WriteSymbol", "symbol token without defined text or symbol id is invalid"}
+	}
+
+	return w.writeSymbolFromID("Writer.WriteSymbol", id)
+}
+
+// WriteSymbolFromString writes a symbol value given a string that is expected to be in the symbol table.
+// Returns an error if string is not in symbol table.
+func (w *binaryWriter) WriteSymbolFromString(val string) error {
+	var id uint64
+	id, w.err = w.resolve("Writer.WriteSymbolFromString", val)
+	if w.err != nil {
+		return w.err
 	}
 
+	return w.writeSymbolFromID("Writer.WriteSymbolFromString", id)
+}
+
+func (w *binaryWriter) writeSymbolFromID(api string, id uint64) error {
 	vlength := uintLen(id)
 	bufLength := vlength + tagLen(vlength)
 	buf := make([]byte, 0, bufLength)
@@ -266,7 +286,7 @@ func (w *binaryWriter) WriteSymbol(val string) error {
 	buf = appendTag(buf, 0x70, vlength)
 	buf = appendUint(buf, id)
 
-	return w.writeValue("Writer.WriteSymbol", buf)
+	return w.writeValue(api, buf)
 }
 
 // WriteString writes a string.
@@ -492,9 +512,17 @@ func (w *binaryWriter) beginValue(api string) error {
 			return &UsageError{api, "field name not set"}
 		}
 
-		id, err := w.resolve(api, *name)
-		if err != nil {
-			return err
+		var id uint64
+		if name.LocalSID != SymbolIDUnknown {
+			id = uint64(name.LocalSID)
+		} else if name.Text != nil {
+			var err error
+			id, err = w.resolve(api, *name.Text)
+			if err != nil {
+				return err
+			}
+		} else {
+			return &UsageError{api, "field name symbol token does not have defined text or symbol id."}
 		}
 
 		buf := make([]byte, 0, 10)
@@ -591,13 +619,14 @@ func (w *binaryWriter) end(api string, t ctx) error {
 
 // Resolve resolves a symbol to its ID.
 func (w *binaryWriter) resolve(api, sym string) (uint64, error) {
-	if strings.HasPrefix(sym, "$") {
-		id, err := strconv.ParseUint(sym[1:], 10, 64)
-		if err == nil {
-			return id, nil
-		}
+	if id, ok := symbolIdentifier(sym); ok {
+		return uint64(id), nil
 	}
 
+	return w.resolveFromSymbolTable(api, sym)
+}
+
+func (w *binaryWriter) resolveFromSymbolTable(api, sym string) (uint64, error) {
 	if w.lst != nil {
 		id, ok := w.lst.FindByName(sym)
 		if !ok {
diff --git a/ion/decimal.go b/ion/decimal.go
index 703774a..b995b95 100644
--- a/ion/decimal.go
+++ b/ion/decimal.go
@@ -229,8 +229,6 @@ func rescale(a, b *Decimal) (*Decimal, *Decimal) {
 	}
 }
 
-var ten = big.NewInt(10)
-
 // Make 'n' bigger by making 'scale' smaller, since we know we can
 // do that. (1d100 -> 10d99). Makes comparisons and math easier, at the
 // expense of more storage space. Technically speaking implies adding
@@ -241,7 +239,7 @@ func (d *Decimal) upscale(scale int32) *Decimal {
 		panic("can't upscale to a smaller scale")
 	}
 
-	pow := new(big.Int).Exp(ten, big.NewInt(diff), nil)
+	pow := new(big.Int).Exp(big.NewInt(10), big.NewInt(diff), nil)
 	n := new(big.Int).Mul(d.n, pow)
 
 	return &Decimal{
@@ -263,7 +261,7 @@ func (d *Decimal) checkToUpscale() (*Decimal, error) {
 				Err:  strconv.ErrRange,
 			}
 		}
-		d = d.upscale(0)
+		return d.upscale(0), nil
 	}
 	return d, nil
 }
@@ -274,10 +272,9 @@ func (d *Decimal) trunc() (int64, error) {
 	if err != nil {
 		return 0, err
 	}
-	d = ud
-	str := d.n.String()
+	str := ud.n.String()
 
-	truncateTo := len(str) - int(d.scale)
+	truncateTo := len(str) - int(ud.scale)
 	if truncateTo <= 0 {
 		return 0, nil
 	}
@@ -291,9 +288,8 @@ func (d *Decimal) round() (int64, error) {
 	if err != nil {
 		return 0, err
 	}
-	d = ud
 
-	floatValue := float64(d.n.Int64()) / math.Pow10(int(d.scale))
+	floatValue := float64(ud.n.Int64()) / math.Pow10(int(ud.scale))
 	roundedValue := math.Round(floatValue)
 	return int64(roundedValue), nil
 }
diff --git a/ion/marshal.go b/ion/marshal.go
index 9931ba1..cfcc80a 100644
--- a/ion/marshal.go
+++ b/ion/marshal.go
@@ -226,7 +226,7 @@ func (m *Encoder) encodeValue(v reflect.Value, hint Type) error {
 
 	case reflect.String:
 		if hint == SymbolType {
-			return m.w.WriteSymbol(v.String())
+			return m.w.WriteSymbolFromString(v.String())
 		}
 		return m.w.WriteString(v.String())
 
@@ -276,10 +276,11 @@ func (m *Encoder) encodeMap(v reflect.Value, hint Type) error {
 	}
 
 	for _, key := range keys {
-		err = m.w.FieldName(key.s)
+		err = m.w.FieldName(NewSymbolTokenFromString(key.s))
 		if err != nil {
 			return err
 		}
+
 		value := v.MapIndex(key.v)
 		if err := m.encodeValue(value, hint); err != nil {
 			return err
@@ -405,7 +406,7 @@ FieldLoop:
 			continue
 		}
 
-		if err := m.w.FieldName(f.name); err != nil {
+		if err := m.w.FieldName(NewSymbolTokenFromString(f.name)); err != nil {
 			return err
 		}
 		if err := m.encodeValue(fv, f.hint); err != nil {
diff --git a/ion/reader.go b/ion/reader.go
index 42417fc..05005c0 100644
--- a/ion/reader.go
+++ b/ion/reader.go
@@ -73,7 +73,6 @@ import (
 // 	}
 //
 type Reader interface {
-
 	// Next advances the Reader to the next position in the current value stream.
 	// It returns true if this is the position of an Ion value, and false if it
 	// is not. On error, it returns false and sets Err.
@@ -361,9 +360,7 @@ func (r *reader) ByteValue() ([]byte, error) {
 
 // Clear clears the current value from the reader.
 func (r *reader) clear() {
-	if r.fieldName != nil {
-		r.fieldName.Text = nil
-	}
+	r.fieldName = nil
 	r.annotations = nil
 	r.valueType = NoType
 	r.value = nil
diff --git a/ion/readlocalsymboltable.go b/ion/readlocalsymboltable.go
index ed60b47..ac8060b 100644
--- a/ion/readlocalsymboltable.go
+++ b/ion/readlocalsymboltable.go
@@ -106,7 +106,6 @@ func readImport(r Reader, cat Catalog) (SharedSymbolTable, error) {
 	maxID := int64(-1)
 
 	for r.Next() {
-		var err error
 		fieldName, err := r.FieldName()
 		if err != nil {
 			return nil, err
@@ -118,16 +117,19 @@ func readImport(r Reader, cat Catalog) (SharedSymbolTable, error) {
 		switch *fieldName.Text {
 		case "name":
 			if r.Type() == StringType {
-				var val *string
-				val, err = r.StringValue()
-				if val != nil {
-					name = *val
+				val, err := r.StringValue()
+				if err != nil {
+					return nil, err
 				}
+				name = *val
 			}
 		case "version":
 			if r.Type() == IntType {
-				val, er := r.IntValue()
-				version, err = *val, er
+				val, err := r.IntValue()
+				if err != nil {
+					return nil, err
+				}
+				version = *val
 			}
 		case "max_id":
 			if r.Type() == IntType {
@@ -141,9 +143,6 @@ func readImport(r Reader, cat Catalog) (SharedSymbolTable, error) {
 				maxID = *i
 			}
 		}
-		if err != nil {
-			return nil, err
-		}
 	}
 
 	if err := r.StepOut(); err != nil {
diff --git a/ion/symboltable.go b/ion/symboltable.go
index 0d57717..dacac10 100644
--- a/ion/symboltable.go
+++ b/ion/symboltable.go
@@ -164,21 +164,33 @@ func (s *sst) WriteTo(w Writer) error {
 		return err
 	}
 	{
-		if err := w.FieldName("name"); err != nil {
+		st, err := NewSymbolToken(s, "name")
+		if err != nil {
+			return err
+		}
+		if err := w.FieldName(st); err != nil {
 			return err
 		}
 		if err := w.WriteString(s.name); err != nil {
 			return err
 		}
 
-		if err := w.FieldName("version"); err != nil {
+		st, err = NewSymbolToken(s, "version")
+		if err != nil {
+			return err
+		}
+		if err := w.FieldName(st); err != nil {
 			return err
 		}
 		if err := w.WriteInt(int64(s.version)); err != nil {
 			return err
 		}
 
-		if err := w.FieldName("symbols"); err != nil {
+		st, err = NewSymbolToken(s, "symbols")
+		if err != nil {
+			return err
+		}
+		if err := w.FieldName(st); err != nil {
 			return err
 		}
 		if err := w.BeginList(); err != nil {
@@ -202,7 +214,7 @@ func (s *sst) String() string {
 	buf := strings.Builder{}
 
 	w := NewTextWriter(&buf)
-	s.WriteTo(w)
+	_ = s.WriteTo(w)
 
 	return buf.String()
 }
@@ -281,19 +293,22 @@ func (s *bogusSST) String() string {
 
 	buf := strings.Builder{}
 	w := NewTextWriter(&buf)
-	w.Annotations(SymbolToken{Text: &ionSharedSymbolTableText, LocalSID: 9}, SymbolToken{Text: &bogusText, LocalSID: SymbolIDUnknown})
-	w.BeginStruct()
+	_ = w.Annotations(SymbolToken{Text: &ionSharedSymbolTableText, LocalSID: 9}, SymbolToken{Text: &bogusText, LocalSID: SymbolIDUnknown})
+	_ = w.BeginStruct()
 
-	w.FieldName("name")
-	w.WriteString(s.name)
+	st, _ := NewSymbolToken(s, "name")
+	_ = w.FieldName(st)
+	_ = w.WriteString(s.name)
 
-	w.FieldName("version")
-	w.WriteInt(int64(s.version))
+	st, _ = NewSymbolToken(s, "version")
+	_ = w.FieldName(st)
+	_ = w.WriteInt(int64(s.version))
 
-	w.FieldName("max_id")
-	w.WriteUint(s.maxID)
+	st, _ = NewSymbolToken(s, "max_id")
+	_ = w.FieldName(st)
+	_ = w.WriteUint(s.maxID)
 
-	w.EndStruct()
+	_ = w.EndStruct()
 	return buf.String()
 }
 
@@ -351,8 +366,7 @@ func (t *lst) Find(s string) *SymbolToken {
 	}
 
 	// Check local
-	_, ok := t.index[s]
-	if ok {
+	if _, ok := t.index[s]; ok {
 		return &SymbolToken{Text: &s, LocalSID: SymbolIDUnknown}
 	}
 
@@ -418,7 +432,11 @@ func (t *lst) WriteTo(w Writer) error {
 	}
 
 	if len(t.imports) > 1 {
-		if err := w.FieldName("imports"); err != nil {
+		st, err := NewSymbolToken(t, "imports")
+		if err != nil {
+			return err
+		}
+		if err := w.FieldName(st); err != nil {
 			return err
 		}
 		if err := w.BeginList(); err != nil {
@@ -430,21 +448,33 @@ func (t *lst) WriteTo(w Writer) error {
 				return err
 			}
 
-			if err := w.FieldName("name"); err != nil {
+			st, err := NewSymbolToken(t, "name")
+			if err != nil {
+				return err
+			}
+			if err := w.FieldName(st); err != nil {
 				return err
 			}
 			if err := w.WriteString(imp.Name()); err != nil {
 				return err
 			}
 
-			if err := w.FieldName("version"); err != nil {
+			st, err = NewSymbolToken(t, "version")
+			if err != nil {
+				return err
+			}
+			if err := w.FieldName(st); err != nil {
 				return err
 			}
 			if err := w.WriteInt(int64(imp.Version())); err != nil {
 				return err
 			}
 
-			if err := w.FieldName("max_id"); err != nil {
+			st, err = NewSymbolToken(t, "max_id")
+			if err != nil {
+				return err
+			}
+			if err := w.FieldName(st); err != nil {
 				return err
 			}
 			if err := w.WriteUint(imp.MaxID()); err != nil {
@@ -461,7 +491,11 @@ func (t *lst) WriteTo(w Writer) error {
 	}
 
 	if len(t.symbols) > 0 {
-		if err := w.FieldName("symbols"); err != nil {
+		st, err := NewSymbolToken(t, "symbols")
+		if err != nil {
+			return err
+		}
+		if err := w.FieldName(st); err != nil {
 			return err
 		}
 
@@ -485,7 +519,7 @@ func (t *lst) String() string {
 	buf := strings.Builder{}
 
 	w := NewTextWriter(&buf)
-	t.WriteTo(w)
+	_ = t.WriteTo(w)
 
 	return buf.String()
 }
diff --git a/ion/symboltoken.go b/ion/symboltoken.go
index 5a1739a..8ea9f15 100644
--- a/ion/symboltoken.go
+++ b/ion/symboltoken.go
@@ -27,7 +27,6 @@ const (
 
 // ImportSource is a reference to a SID within a shared symbol table.
 type ImportSource struct {
-
 	// The name of the shared symbol table that this token refers to.
 	Table string
 
@@ -103,6 +102,15 @@ func symbolIdentifier(symbolText string) (int64, bool) {
 	return SymbolIDUnknown, false
 }
 
+// NewSymbolTokenFromString returns a Symbol Token with the given text value and undefined SID and Source.
+func NewSymbolTokenFromString(text string) SymbolToken {
+	return SymbolToken{Text: &text, LocalSID: SymbolIDUnknown}
+}
+
+func newSymbolTokenPtrFromString(text string) *SymbolToken {
+	return &SymbolToken{Text: &text, LocalSID: SymbolIDUnknown}
+}
+
 // NewSymbolTokenBySID will check and return a symbol token if the given id exists in a symbol table,
 // otherwise return a new symbol token.
 func NewSymbolTokenBySID(symbolTable SymbolTable, sid int64) (SymbolToken, error) {
@@ -125,10 +133,6 @@ func NewSymbolToken(symbolTable SymbolTable, text string) (SymbolToken, error) {
 		return SymbolToken{}, fmt.Errorf("ion: invalid symbol table")
 	}
 
-	if sid, ok := symbolIdentifier(text); ok {
-		return NewSymbolTokenBySID(symbolTable, sid)
-	}
-
 	sid, ok := symbolTable.FindByName(text)
 	if !ok {
 		return SymbolToken{Text: &text, LocalSID: SymbolIDUnknown}, nil
@@ -152,3 +156,10 @@ func NewSymbolTokens(symbolTable SymbolTable, textVals []string) ([]SymbolToken,
 
 	return tokens, nil
 }
+
+func newSymbolToken(symbolTable SymbolTable, text string) (SymbolToken, error) {
+	if sid, ok := symbolIdentifier(text); ok {
+		return NewSymbolTokenBySID(symbolTable, sid)
+	}
+	return NewSymbolToken(symbolTable, text)
+}
diff --git a/ion/textreader.go b/ion/textreader.go
index 49a3b1f..bbbb2ee 100644
--- a/ion/textreader.go
+++ b/ion/textreader.go
@@ -185,7 +185,7 @@ func (t *textReader) nextBeforeFieldName() (bool, error) {
 		if tok == tokenSymbolQuoted {
 			t.fieldName = &SymbolToken{Text: &val, LocalSID: SymbolIDUnknown}
 		} else {
-			st, err := NewSymbolToken(t.SymbolTable(), val)
+			st, err := newSymbolToken(t.SymbolTable(), val)
 			if err != nil {
 				return false, err
 			}
@@ -250,13 +250,14 @@ func (t *textReader) nextBeforeTypeAnnotations() (bool, error) {
 					"annotations that include a '" + val + "' must be enclosed in quotes", t.tok.Pos() - 1}
 			}
 
-			token, err := NewSymbolToken(t.SymbolTable(), val)
-			if err != nil {
-				return false, err
-			}
-
+			var token SymbolToken
 			if tok == tokenSymbolQuoted {
 				token = SymbolToken{Text: &val, LocalSID: SymbolIDUnknown}
+			} else {
+				token, err = newSymbolToken(t.SymbolTable(), val)
+				if err != nil {
+					return false, err
+				}
 			}
 
 			t.annotations = append(t.annotations, token)
@@ -454,11 +455,11 @@ func (t *textReader) onSymbol(val string, tok token, ws bool) error {
 			valueType = FloatType
 			value = math.NaN()
 		default:
-			val, err := NewSymbolToken(t.SymbolTable(), val)
+			st, err := newSymbolToken(t.SymbolTable(), val)
 			if err != nil {
 				return err
 			}
-			value = &val
+			value = &st
 		}
 	}
 
diff --git a/ion/textutils.go b/ion/textutils.go
index ca94522..0092cd5 100644
--- a/ion/textutils.go
+++ b/ion/textutils.go
@@ -16,6 +16,7 @@
 package ion
 
 import (
+	"fmt"
 	"io"
 	"math/big"
 	"strconv"
@@ -139,8 +140,34 @@ func formatFloat(val float64) string {
 	return str
 }
 
+// Write the given symbol out.
+func writeSymbol(val interface{}, out io.Writer) error {
+	token := val.(SymbolToken)
+
+	var text string
+	if token.Text != nil {
+		text = *token.Text
+
+		if _, ok := symbolIdentifier(text); ok {
+			// Wrap text value in single quotes if the symbol's text is a symbol identifier
+			// (ie. of form $n for some integer n)
+			// This is done to distinguish from actual symbol table mappings.
+			text = fmt.Sprintf("'%v'", text)
+			return writeRawString(text, out)
+		}
+	} else if token.LocalSID != SymbolIDUnknown {
+		text = fmt.Sprintf("$%v", token.LocalSID)
+	} else {
+		return fmt.Errorf("ion: invalid symbol token")
+	}
+
+	return writeSymbolFromString(text, out)
+}
+
 // Write the given symbol out, quoting and encoding if necessary.
-func writeSymbol(sym string, out io.Writer) error {
+func writeSymbolFromString(val interface{}, out io.Writer) error {
+	sym := val.(string)
+
 	if symbolNeedsQuoting(sym) {
 		if err := writeRawChar('\'', out); err != nil {
 			return err
@@ -219,7 +246,9 @@ func writeEscapedChar(c byte, out io.Writer) error {
 }
 
 // Write out the given raw string.
-func writeRawString(s string, out io.Writer) error {
+func writeRawString(val interface{}, out io.Writer) error {
+	s := val.(string)
+
 	_, err := out.Write([]byte(s))
 	return err
 }
@@ -298,5 +327,5 @@ func parseInt(str string, radix int) (interface{}, error) {
 }
 
 func parseTimestamp(val string) (Timestamp, error) {
-	return ParseTimestampFromStr(val)
+	return ParseTimestamp(val)
 }
diff --git a/ion/textwriter.go b/ion/textwriter.go
index 401fba5..3b31b1e 100644
--- a/ion/textwriter.go
+++ b/ion/textwriter.go
@@ -66,12 +66,12 @@ func NewTextWriterOpts(out io.Writer, opts TextWriterOpts, sts ...SharedSymbolTa
 
 // WriteNull writes an untyped null.
 func (w *textWriter) WriteNull() error {
-	return w.writeValue("Writer.WriteNull", textNulls[NoType])
+	return w.writeValue("Writer.WriteNull", textNulls[NoType], writeRawString)
 }
 
 // WriteNullType writes a typed null.
 func (w *textWriter) WriteNullType(t Type) error {
-	return w.writeValue("Writer.WriteNullType", textNulls[t])
+	return w.writeValue("Writer.WriteNullType", textNulls[t], writeRawString)
 }
 
 // WriteBool writes a boolean value.
@@ -80,54 +80,47 @@ func (w *textWriter) WriteBool(val bool) error {
 	if val {
 		str = "true"
 	}
-	return w.writeValue("Writer.WriteBool", str)
+	return w.writeValue("Writer.WriteBool", str, writeRawString)
 }
 
 // WriteInt writes an integer value.
 func (w *textWriter) WriteInt(val int64) error {
-	return w.writeValue("Writer.WriteInt", fmt.Sprintf("%d", val))
+	return w.writeValue("Writer.WriteInt", fmt.Sprintf("%d", val), writeRawString)
 }
 
 // WriteUint writes an unsigned integer value.
 func (w *textWriter) WriteUint(val uint64) error {
-	return w.writeValue("Writer.WriteUint", fmt.Sprintf("%d", val))
+	return w.writeValue("Writer.WriteUint", fmt.Sprintf("%d", val), writeRawString)
 }
 
 // WriteBigInt writes a (big) integer value.
 func (w *textWriter) WriteBigInt(val *big.Int) error {
-	return w.writeValue("Writer.WriteBigInt", val.String())
+	return w.writeValue("Writer.WriteBigInt", val.String(), writeRawString)
 }
 
 // WriteFloat writes a floating-point value.
 func (w *textWriter) WriteFloat(val float64) error {
-	return w.writeValue("Writer.WriteFloat", formatFloat(val))
+	return w.writeValue("Writer.WriteFloat", formatFloat(val), writeRawString)
 }
 
 // WriteDecimal writes an arbitrary-precision decimal value.
 func (w *textWriter) WriteDecimal(val *Decimal) error {
-	return w.writeValue("Writer.WriteDecimal", val.String())
+	return w.writeValue("Writer.WriteDecimal", val.String(), writeRawString)
 }
 
 // WriteTimestamp writes a timestamp.
 func (w *textWriter) WriteTimestamp(val Timestamp) error {
-	return w.writeValue("Writer.WriteTimestamp", val.String())
+	return w.writeValue("Writer.WriteTimestamp", val.String(), writeRawString)
 }
 
-// WriteSymbol writes a symbol.
-func (w *textWriter) WriteSymbol(val string) error {
-	if w.err != nil {
-		return w.err
-	}
-	if w.err = w.beginValue("Writer.WriteSymbol"); w.err != nil {
-		return w.err
-	}
-
-	if w.err = writeSymbol(val, w.out); w.err != nil {
-		return w.err
-	}
+// WriteSymbol writes a symbol given a SymbolToken.
+func (w *textWriter) WriteSymbol(val SymbolToken) error {
+	return w.writeValue("Writer.WriteSymbol", val, writeSymbol)
+}
 
-	w.endValue()
-	return nil
+// WriteSymbolFromString writes a symbol given a string.
+func (w *textWriter) WriteSymbolFromString(val string) error {
+	return w.writeValue("Writer.WriteSymbolFromString", val, writeSymbolFromString)
 }
 
 // WriteString writes a string.
@@ -289,7 +282,7 @@ func (w *textWriter) pretty() bool {
 }
 
 // writeValue writes a stringified value to the output stream.
-func (w *textWriter) writeValue(api string, val string) error {
+func (w *textWriter) writeValue(api string, val interface{}, fn func(interface{}, io.Writer) error) error {
 	if w.err != nil {
 		return w.err
 	}
@@ -297,7 +290,7 @@ func (w *textWriter) writeValue(api string, val string) error {
 		return w.err
 	}
 
-	if w.err = writeRawString(val, w.out); w.err != nil {
+	if w.err = fn(val, w.out); w.err != nil {
 		return w.err
 	}
 
@@ -353,7 +346,7 @@ func (w *textWriter) beginValue(api string) error {
 
 	w.annotations = append(w.annotations, as...)
 	if len(w.annotations) > 0 {
-		if err := w.writeAnnotations(api); err != nil {
+		if err := w.writeAnnotations(); err != nil {
 			return err
 		}
 	}
@@ -411,21 +404,12 @@ func (w *textWriter) writeFieldName(api string) error {
 }
 
 // writeAnnotations writes out the annotations for a value.
-func (w *textWriter) writeAnnotations(api string) error {
+func (w *textWriter) writeAnnotations() error {
 	as := w.annotations
 	w.annotations = nil
 
-	var text string
 	for _, a := range as {
-		if a.Text != nil {
-			text = *a.Text
-		} else if a.LocalSID != SymbolIDUnknown {
-			text = fmt.Sprintf("$%v", a.LocalSID)
-		} else {
-			return &UsageError{api, "invalid annotation symbol token"}
-		}
-
-		if err := writeSymbol(text, w.out); err != nil {
+		if err := writeSymbol(a, w.out); err != nil {
 			return err
 		}
 		if err := writeRawString("::", w.out); err != nil {
diff --git a/ion/timestamp.go b/ion/timestamp.go
index 8fd26cc..96c6155 100644
--- a/ion/timestamp.go
+++ b/ion/timestamp.go
@@ -240,8 +240,18 @@ func tryCreateTimestamp(ts []int, nsecs int, overflow bool, offset, sign int64,
 	return NewTimestampWithFractionalSeconds(date, precision, TimezoneLocal, fractionPrecision), nil
 }
 
-// ParseTimestampFromStr parses a timestamp string and returns an ion timestamp.
-func ParseTimestampFromStr(dateStr string) (Timestamp, error) {
+// MustParseTimestamp parses the given string into an ion timestamp object,
+// panicking on error.
+func MustParseTimestamp(dateStr string) Timestamp {
+	ts, err := ParseTimestamp(dateStr)
+	if err != nil {
+		panic(err)
+	}
+	return ts
+}
+
+// ParseTimestamp parses a timestamp string and returns an ion timestamp.
+func ParseTimestamp(dateStr string) (Timestamp, error) {
 	if len(dateStr) < 5 {
 		return invalidTimestamp(dateStr)
 	}
diff --git a/ion/tokenizer.go b/ion/tokenizer.go
index d9eff19..77b2fa5 100644
--- a/ion/tokenizer.go
+++ b/ion/tokenizer.go
@@ -1428,14 +1428,14 @@ func isProhibitedControlChar(c int) bool {
 }
 
 func isStringWhitespace(c int) bool {
-	return c == 0x09 || //horizontal tab
-		c == 0x0B || //vertical tab
+	return c == 0x09 || // horizontal tab
+		c == 0x0B || // vertical tab
 		c == 0x0C // form feed
 }
 
 func isNewLineChar(c int) bool {
-	return c == 0x0A || //new line
-		c == 0x0D //carriage return
+	return c == 0x0A || // new line
+		c == 0x0D // carriage return
 }
 
 // isASCII returns true if c is a 7-bit ASCII character.
diff --git a/ion/unmarshal.go b/ion/unmarshal.go
index a81cc44..3e2b005 100644
--- a/ion/unmarshal.go
+++ b/ion/unmarshal.go
@@ -92,8 +92,9 @@ var typesAcceptableKinds = map[Type][]reflect.Kind{
 //     []interface{}{}                                 sexp
 //     map[string]interface{}{}/struct/interface{}     struct
 //
-func Unmarshal(data []byte, v interface{}) error {
-	return NewDecoder(NewReader(bytes.NewReader(data))).DecodeTo(v)
+func Unmarshal(data []byte, v interface{}, ssts ...SharedSymbolTable) error {
+	catalog := NewCatalog(ssts...)
+	return NewDecoder(NewReaderCat(bytes.NewReader(data), catalog)).DecodeTo(v)
 }
 
 // UnmarshalString unmarshals Ion data from a string to the given object.
@@ -151,6 +152,9 @@ func (d *Decoder) decode() (interface{}, error) {
 	switch d.r.Type() {
 	case BoolType:
 		val, err := d.r.BoolValue()
+		if err != nil {
+			return nil, err
+		}
 		return *val, err
 
 	case IntType:
@@ -196,9 +200,15 @@ func (d *Decoder) decodeInt() (interface{}, error) {
 		return nil, nil
 	case Int32:
 		val, err := d.r.IntValue()
+		if err != nil {
+			return nil, err
+		}
 		return *val, err
 	case Int64:
 		val, err := d.r.Int64Value()
+		if err != nil {
+			return nil, err
+		}
 		return *val, err
 	default:
 		return d.r.BigIntValue()
diff --git a/ion/writer.go b/ion/writer.go
index da817f0..e2b251a 100644
--- a/ion/writer.go
+++ b/ion/writer.go
@@ -16,7 +16,6 @@
 package ion
 
 import (
-	"fmt"
 	"io"
 	"math/big"
 )
@@ -32,7 +31,7 @@ import (
 // 	w.BeginSexp()
 // 	{
 // 		w.WriteInt(1)
-// 		w.WriteSymbol("+")
+// 		w.WriteSymbolFromString("+")
 // 		w.WriteInt(1)
 // 	}
 // 	w.EndSexp()
@@ -65,12 +64,8 @@ import (
 // 	}
 //
 type Writer interface {
-
 	// FieldName sets the field name for the next value written.
-	FieldName(val string) error
-
-	// FieldName sets the field name symbol for the next value written.
-	FieldNameSymbol(val SymbolToken) error
+	FieldName(val SymbolToken) error
 
 	// Annotation adds a single annotation to the next value written.
 	Annotation(val SymbolToken) error
@@ -105,8 +100,11 @@ type Writer interface {
 	// WriteTimestamp writes a timestamp value.
 	WriteTimestamp(val Timestamp) error
 
-	// WriteSymbol writes a symbol value.
-	WriteSymbol(val string) error
+	// WriteSymbol writes a symbol value given a SymbolToken.
+	WriteSymbol(val SymbolToken) error
+
+	// WriteSymbolFromString writes a symbol value given a string.
+	WriteSymbolFromString(val string) error
 
 	// WriteString writes a string value.
 	WriteString(val string) error
@@ -148,13 +146,13 @@ type writer struct {
 	ctx ctxstack
 	err error
 
-	fieldName   *string
+	fieldName   *SymbolToken
 	annotations []SymbolToken
 }
 
-// FieldName sets the field name for the next value written.
+// FieldName sets the field name symbol for the next value written.
 // It may only be called while writing a struct.
-func (w *writer) FieldName(val string) error {
+func (w *writer) FieldName(val SymbolToken) error {
 	if w.err != nil {
 		return w.err
 	}
@@ -167,24 +165,6 @@ func (w *writer) FieldName(val string) error {
 	return nil
 }
 
-// FieldNameSymbol sets the field name symbol for the next value written.
-// It may only be called while writing a struct.
-func (w *writer) FieldNameSymbol(val SymbolToken) error {
-	if !w.IsInStruct() {
-		w.err = &UsageError{"Writer.FieldNameSymbol", "called when not writing a struct"}
-		return w.err
-	}
-
-	if val.Text == nil {
-		sid := fmt.Sprintf("$%v", val.LocalSID)
-		w.fieldName = &sid
-		return nil
-	}
-
-	w.fieldName = val.Text
-	return nil
-}
-
 // Annotation adds an annotation to the next value written.
 func (w *writer) Annotation(val SymbolToken) error {
 	if w.err != nil {


Test patch:
diff --git a/ion/binaryreader_test.go b/ion/binaryreader_test.go
index a78ccdf..57dd86a 100644
--- a/ion/binaryreader_test.go
+++ b/ion/binaryreader_test.go
@@ -126,14 +126,15 @@ func TestReadBinaryStructs(t *testing.T) {
 	_struct(t, r, func(t *testing.T, r Reader) {
 		_eof(t, r)
 	})
-	_structAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}}, func(t *testing.T, r Reader) {
-		_structAF(t, r, &SymbolToken{Text: newString("name"), LocalSID: 4}, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, func(t *testing.T, r Reader) {
+	_structAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("foo")}, func(t *testing.T, r Reader) {
+		_structAF(t, r, &SymbolToken{Text: newString("name"), LocalSID: 4}, []SymbolToken{NewSymbolTokenFromString("bar")}, func(t *testing.T, r Reader) {
 			_eof(t, r)
 		})
 		_intAF(t, r, &SymbolToken{Text: newString("max_id"), LocalSID: 8}, nil, 0)
 	})
 	_structAF(t, r, nil, nil, func(t *testing.T, r Reader) {
-		_intAF(t, r, &SymbolToken{Text: newString(""), LocalSID: SymbolIDUnknown}, nil, 15)
+		st := NewSymbolTokenFromString("")
+		_intAF(t, r, &st, nil, 15)
 	})
 	_eof(t, r)
 }
@@ -257,7 +258,7 @@ func TestReadBinaryNullFieldName(t *testing.T) {
 		// }
 	})
 	r.Next()
-	r.StepIn()
+	assert.NoError(t, r.StepIn())
 	_nextF(t, r, &SymbolToken{}, true, true)
 }
 
@@ -287,10 +288,10 @@ func TestReadBinaryAnnotations(t *testing.T) {
 		0xE3, 0x81, 0xF1, 0x0F, // $113::null
 	})
 
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: nil, LocalSID: 0}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("$ion"), LocalSID: 1}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: 110}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: 111}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: nil, LocalSID: 0}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("$ion"), LocalSID: 1}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("foo"), LocalSID: 110}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("bar"), LocalSID: 111}}, false, false)
 	_nextA(t, r, nil, true, true)
 }
 
@@ -455,8 +456,8 @@ func TestReadBinaryNulls(t *testing.T) {
 	})
 
 	_null(t, r, NullType)
-	_nullAF(t, r, NullType, nil, []SymbolToken{SymbolToken{Text: newString("$ion"), LocalSID: 1}})
-	_nullAF(t, r, NullType, nil, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}})
+	_nullAF(t, r, NullType, nil, []SymbolToken{{Text: newString("$ion"), LocalSID: 1}})
+	_nullAF(t, r, NullType, nil, []SymbolToken{NewSymbolTokenFromString("foo"), NewSymbolTokenFromString("bar")})
 	_eof(t, r)
 }
 
diff --git a/ion/binarywriter_test.go b/ion/binarywriter_test.go
index bb8b92e..f94ef1d 100644
--- a/ion/binarywriter_test.go
+++ b/ion/binarywriter_test.go
@@ -41,14 +41,14 @@ func TestWriteBinaryStruct(t *testing.T) {
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("foo")))
 		assert.NoError(t, w.BeginStruct())
 		{
-			assert.NoError(t, w.FieldName("name"))
-			assert.NoError(t, w.Annotation(SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}))
+			assert.NoError(t, w.FieldName(NewSymbolTokenFromString("name")))
+			assert.NoError(t, w.Annotation(NewSymbolTokenFromString("bar")))
 			assert.NoError(t, w.WriteNull())
 
-			assert.NoError(t, w.FieldName("max_id"))
+			assert.NoError(t, w.FieldName(NewSymbolTokenFromString("max_id")))
 			assert.NoError(t, w.WriteInt(0))
 		}
 		assert.NoError(t, w.EndStruct())
@@ -67,10 +67,10 @@ func TestWriteBinarySexp(t *testing.T) {
 		assert.NoError(t, w.BeginSexp())
 		assert.NoError(t, w.EndSexp())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("foo")))
 		assert.NoError(t, w.BeginSexp())
 		{
-			assert.NoError(t, w.Annotation(SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}))
+			assert.NoError(t, w.Annotation(NewSymbolTokenFromString("bar")))
 			assert.NoError(t, w.WriteNull())
 
 			assert.NoError(t, w.WriteInt(0))
@@ -91,10 +91,10 @@ func TestWriteBinaryList(t *testing.T) {
 		assert.NoError(t, w.BeginList())
 		assert.NoError(t, w.EndList())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("foo")))
 		assert.NoError(t, w.BeginList())
 		{
-			assert.NoError(t, w.Annotation(SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}))
+			assert.NoError(t, w.Annotation(NewSymbolTokenFromString("bar")))
 			assert.NoError(t, w.WriteNull())
 
 			assert.NoError(t, w.WriteInt(0))
@@ -160,11 +160,11 @@ func TestWriteBinarySymbol(t *testing.T) {
 		0x74, 0xFF, 0xFF, 0xFF, 0xFF, // $4294967295
 	}
 	testBinaryWriter(t, eval, func(w Writer) {
-		assert.NoError(t, w.WriteSymbol("$ion"))
-		assert.NoError(t, w.WriteSymbol("name"))
-		assert.NoError(t, w.WriteSymbol("version"))
-		assert.NoError(t, w.WriteSymbol("$ion_shared_symbol_table"))
-		assert.NoError(t, w.WriteSymbol("$4294967295"))
+		assert.NoError(t, w.WriteSymbolFromString("$ion"))
+		assert.NoError(t, w.WriteSymbolFromString("name"))
+		assert.NoError(t, w.WriteSymbolFromString("version"))
+		assert.NoError(t, w.WriteSymbolFromString("$ion_shared_symbol_table"))
+		assert.NoError(t, w.WriteSymbolFromString("$4294967295"))
 	})
 }
 
diff --git a/ion/catalog_test.go b/ion/catalog_test.go
index 21abb42..4b01847 100644
--- a/ion/catalog_test.go
+++ b/ion/catalog_test.go
@@ -42,7 +42,7 @@ func TestCatalog(t *testing.T) {
 	out := NewBinaryWriter(&buf, sst)
 
 	for i := 0; i < 10; i++ {
-		assert.NoError(t, out.Annotation(SymbolToken{Text: newString("item"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, out.Annotation(NewSymbolTokenFromString("item")))
 		assert.NoError(t,
 			MarshalTo(out, &Item{
 				ID:          i,
diff --git a/ion/decimal_test.go b/ion/decimal_test.go
index f2e66a1..11d0fe0 100644
--- a/ion/decimal_test.go
+++ b/ion/decimal_test.go
@@ -96,15 +96,15 @@ func TestParseDecimal(t *testing.T) {
 func absF(d *Decimal) *Decimal { return d.Abs() }
 func negF(d *Decimal) *Decimal { return d.Neg() }
 
-type unaryop struct {
+type unaryOp struct {
 	sym string
 	fun func(d *Decimal) *Decimal
 }
 
-var abs = &unaryop{"abs", absF}
-var neg = &unaryop{"neg", negF}
+var abs = &unaryOp{"abs", absF}
+var neg = &unaryOp{"neg", negF}
 
-func testUnaryOp(t *testing.T, a, e string, op *unaryop) {
+func testUnaryOp(t *testing.T, a, e string, op *unaryOp) {
 	t.Run(op.sym+"("+a+")="+e, func(t *testing.T) {
 		aa, _ := ParseDecimal(a)
 		ee, _ := ParseDecimal(e)
diff --git a/ion/integration_test.go b/ion/integration_test.go
index 9d10679..e53a298 100644
--- a/ion/integration_test.go
+++ b/ion/integration_test.go
@@ -76,9 +76,19 @@ var readGoodFilesSkipList = []string{
 	"whitespace.ion",
 }
 
-var binaryRoundTripSkipList = []string{}
+var binaryRoundTripSkipList = []string{
+	"T7-large.10n",
+	"utf16.ion",
+	"utf32.ion",
+	"whitespace.ion",
+}
 
-var textRoundTripSkipList = []string{}
+var textRoundTripSkipList = []string{
+	"T7-large.10n",
+	"utf16.ion",
+	"utf32.ion",
+	"whitespace.ion",
+}
 
 var malformedIonsSkipList = []string{
 	"invalidVersionMarker_ion_0_0.ion",
@@ -104,7 +114,7 @@ var equivsSkipList = []string{
 	"stringUtf8.ion", // fails on utf-16 surrogate https://github.com/amzn/ion-go/issues/75
 }
 
-var nonEquivsSkipList = []string{}
+var nonEquivsSkipList []string
 
 func TestLoadGood(t *testing.T) {
 	readFilesAndTest(t, goodPath, readGoodFilesSkipList, func(t *testing.T, path string) {
@@ -398,14 +408,11 @@ func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 	for reader.Next() {
 		fns, err := reader.FieldName()
 		if err == nil && reader.IsInStruct() && fns != nil {
-			require.NoError(t, writer.FieldNameSymbol(*fns))
+			require.NoError(t, writer.FieldName(*fns))
 		}
 
 		an, err := reader.Annotations()
-		if err != nil {
-			t.Fatal(err)
-		}
-
+		require.NoError(t, err)
 		if len(an) > 0 {
 			require.NoError(t, writer.Annotations(an...))
 		}
@@ -490,8 +497,8 @@ func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 
 			if val == nil {
 				assert.NoError(t, writer.WriteNullType(SymbolType))
-			} else if val.Text != nil {
-				assert.NoError(t, writer.WriteSymbol(*val.Text), "Something went wrong while writing a Symbol value")
+			} else {
+				assert.NoError(t, writer.WriteSymbol(*val), "Something went wrong while writing a Symbol value")
 			}
 
 		case StringType:
@@ -544,6 +551,8 @@ func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 			require.NoError(t, writer.EndStruct())
 		}
 	}
+
+	assert.NoError(t, reader.Err(), "Something went wrong executing reader.Next()")
 }
 
 // Read the current value in the reader and put that in an ionItem struct (defined in this file).
diff --git a/ion/marshal_test.go b/ion/marshal_test.go
index c07439c..e139f13 100644
--- a/ion/marshal_test.go
+++ b/ion/marshal_test.go
@@ -247,18 +247,18 @@ func TestMarshalHints(t *testing.T) {
 	assert.Equal(t, eval, string(val))
 }
 
-type marshalme uint8
+type marshalMe uint8
 
-var _ Marshaler = marshalme(0)
+var _ Marshaler = marshalMe(0)
 
 const (
-	one marshalme = iota
+	one marshalMe = iota
 	two
 	three
 	four
 )
 
-func (m marshalme) String() string {
+func (m marshalMe) String() string {
 	switch m {
 	case one:
 		return "ONE"
@@ -273,8 +273,8 @@ func (m marshalme) String() string {
 	}
 }
 
-func (m marshalme) MarshalIon(w Writer) error {
-	return w.WriteSymbol(m.String())
+func (m marshalMe) MarshalIon(w Writer) error {
+	return w.WriteSymbolFromString(m.String())
 }
 
 func TestMarshalCustomMarshaler(t *testing.T) {
@@ -282,10 +282,10 @@ func TestMarshalCustomMarshaler(t *testing.T) {
 	enc := NewTextEncoder(&buf)
 
 	require.NoError(t, enc.Encode(one))
-	require.NoError(t, enc.EncodeAs([]marshalme{two, three}, SexpType))
+	require.NoError(t, enc.EncodeAs([]marshalMe{two, three}, SexpType))
 
 	v := struct {
-		Num marshalme `ion:"num"`
+		Num marshalMe `ion:"num"`
 	}{four}
 	require.NoError(t, enc.Encode(v))
 	require.NoError(t, enc.Finish())
@@ -311,7 +311,7 @@ func TestMarshalValuesWithAnnotation(t *testing.T) {
 	}
 
 	buildValue := func(val interface{}) foo {
-		return foo{val, []SymbolToken{SymbolToken{Text: newString("symbols or string"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("annotations"), LocalSID: SymbolIDUnknown}}}
+		return foo{val, []SymbolToken{NewSymbolTokenFromString("symbols or string"), NewSymbolTokenFromString("annotations")}}
 	}
 
 	test(buildValue(nil), "null", "'symbols or string'::annotations::null")
diff --git a/ion/symboltable_test.go b/ion/symboltable_test.go
index baeb45f..610b370 100644
--- a/ion/symboltable_test.go
+++ b/ion/symboltable_test.go
@@ -46,8 +46,8 @@ func TestSharedSymbolTable(t *testing.T) {
 	testFindByID(t, st, 4, "null")
 	testFindByID(t, st, 7, "")
 
-	testFindSymbolToken(t, st, "def", SymbolToken{Text: newString("def"), LocalSID: -1})
-	testFindSymbolToken(t, st, "foo'bar", SymbolToken{Text: newString("foo'bar"), LocalSID: -1})
+	testFindSymbolToken(t, st, "def", NewSymbolTokenFromString("def"))
+	testFindSymbolToken(t, st, "foo'bar", NewSymbolTokenFromString("foo'bar"))
 
 	testString(t, st, `$ion_shared_symbol_table::{name:"test",version:2,symbols:["abc","def","foo'bar","null","def","ghi"]}`)
 }
@@ -68,9 +68,9 @@ func TestLocalSymbolTable(t *testing.T) {
 	testFindByID(t, st, 11, "bar")
 	testFindByID(t, st, 12, "")
 
-	testFindSymbolToken(t, st, "foo", SymbolToken{Text: newString("foo"), LocalSID: -1})
-	testFindSymbolToken(t, st, "bar", SymbolToken{Text: newString("bar"), LocalSID: -1})
-	testFindSymbolToken(t, st, "$ion", SymbolToken{Text: newString("$ion"), LocalSID: -1})
+	testFindSymbolToken(t, st, "foo", NewSymbolTokenFromString("foo"))
+	testFindSymbolToken(t, st, "bar", NewSymbolTokenFromString("bar"))
+	testFindSymbolToken(t, st, "$ion", NewSymbolTokenFromString("$ion"))
 
 	testString(t, st, `$ion_symbol_table::{symbols:["foo","bar"]}`)
 }
@@ -106,10 +106,10 @@ func TestLocalSymbolTableWithImports(t *testing.T) {
 	testFindByID(t, st, 13, "bar2")
 	testFindByID(t, st, 14, "")
 
-	testFindSymbolToken(t, st, "foo", SymbolToken{Text: newString("foo"), LocalSID: -1})
-	testFindSymbolToken(t, st, "bar", SymbolToken{Text: newString("bar"), LocalSID: -1})
-	testFindSymbolToken(t, st, "foo2", SymbolToken{Text: newString("foo2"), LocalSID: -1})
-	testFindSymbolToken(t, st, "bar2", SymbolToken{Text: newString("bar2"), LocalSID: -1})
+	testFindSymbolToken(t, st, "foo", NewSymbolTokenFromString("foo"))
+	testFindSymbolToken(t, st, "bar", NewSymbolTokenFromString("bar"))
+	testFindSymbolToken(t, st, "foo2", NewSymbolTokenFromString("foo2"))
+	testFindSymbolToken(t, st, "bar2", NewSymbolTokenFromString("bar2"))
 
 	testString(t, st, `$ion_symbol_table::{imports:[{name:"shared",version:1,max_id:2}],symbols:["foo2","bar2"]}`)
 }
diff --git a/ion/symboltoken_test.go b/ion/symboltoken_test.go
index a35181a..6262c0c 100644
--- a/ion/symboltoken_test.go
+++ b/ion/symboltoken_test.go
@@ -135,7 +135,7 @@ func TestImportSourceEqualsOperator(t *testing.T) {
 }
 
 func TestNewSymbolTokenThatAlreadyExistInSymbolTable(t *testing.T) {
-	expectedSymbolToken := SymbolToken{Text: newString("$ion"), LocalSID: SymbolIDUnknown}
+	expectedSymbolToken := NewSymbolTokenFromString("$ion")
 
 	actualSymbolToken, err := NewSymbolToken(V1SystemSymbolTable, "$ion")
 	assert.NoError(t, err, "expected NewSymbolToken() to execute without errors")
@@ -144,7 +144,7 @@ func TestNewSymbolTokenThatAlreadyExistInSymbolTable(t *testing.T) {
 }
 
 func TestNewSymbolTokenThatDoesNotExistInSymbolTable(t *testing.T) {
-	expectedSymbolToken := SymbolToken{Text: newString("newToken"), LocalSID: SymbolIDUnknown}
+	expectedSymbolToken := NewSymbolTokenFromString("newToken")
 
 	actualSymbolToken, err := NewSymbolToken(V1SystemSymbolTable, "newToken")
 	assert.NoError(t, err, "expected NewSymbolToken() to execute without errors")
@@ -154,8 +154,8 @@ func TestNewSymbolTokenThatDoesNotExistInSymbolTable(t *testing.T) {
 
 func TestNewSymbolTokensThatAlreadyExistInSymbolTable(t *testing.T) {
 	expectedSymbolTokens := []SymbolToken{
-		{Text: newString("$ion"), LocalSID: SymbolIDUnknown},
-		{Text: newString("$ion_1_0"), LocalSID: SymbolIDUnknown}}
+		NewSymbolTokenFromString("$ion"),
+		NewSymbolTokenFromString("$ion_1_0")}
 
 	actualSymbolTokens, err := NewSymbolTokens(V1SystemSymbolTable, []string{"$ion", "$ion_1_0"})
 	assert.NoError(t, err, "expected NewSymbolTokens() to execute without errors")
@@ -167,8 +167,8 @@ func TestNewSymbolTokensThatAlreadyExistInSymbolTable(t *testing.T) {
 
 func TestNewSymbolTokensThatDoNotExistInSymbolTable(t *testing.T) {
 	expectedSymbolTokens := []SymbolToken{
-		{Text: newString("newToken1"), LocalSID: SymbolIDUnknown},
-		{Text: newString("newToken2"), LocalSID: SymbolIDUnknown}}
+		NewSymbolTokenFromString("newToken1"),
+		NewSymbolTokenFromString("newToken2")}
 
 	actualSymbolTokens, err := NewSymbolTokens(V1SystemSymbolTable, []string{"newToken1", "newToken2"})
 	assert.NoError(t, err, "expected NewSymbolTokens() to execute without errors")
diff --git a/ion/textreader_test.go b/ion/textreader_test.go
index e49f4b3..5df6662 100644
--- a/ion/textreader_test.go
+++ b/ion/textreader_test.go
@@ -43,11 +43,11 @@ func TestReadTextSymbols(t *testing.T) {
 
 	r := NewReaderString(ionText)
 	_symbolAF(t, r, nil, nil, &SymbolToken{Text: nil, LocalSID: 0}, false, false)
-	_symbol(t, r, SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, NewSymbolTokenFromString("$4"))
 	_symbol(t, r, SymbolToken{Text: newString("name"), LocalSID: 4})
 	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: 10})
 	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: 10})
-	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, NewSymbolTokenFromString("bar"))
 	_symbol(t, r, SymbolToken{Text: newString("$ion"), LocalSID: 1})
 	_symbolAF(t, r, nil, nil, nil, false, false)
 	_symbolAF(t, r, nil, nil, &SymbolToken{}, true, true)
@@ -73,13 +73,13 @@ func TestReadTextAnnotations(t *testing.T) {
 	assert.True(t, r.Next())
 	assert.NoError(t, r.StepIn())
 
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: nil, LocalSID: 0}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("name"), LocalSID: 4}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: 10}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: 10}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, false, false)
-	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("$ion"), LocalSID: 1}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: nil, LocalSID: 0}}, false, false)
+	_nextA(t, r, []SymbolToken{NewSymbolTokenFromString("$4")}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("name"), LocalSID: 4}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("foo"), LocalSID: 10}}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("foo"), LocalSID: 10}}, false, false)
+	_nextA(t, r, []SymbolToken{NewSymbolTokenFromString("bar")}, false, false)
+	_nextA(t, r, []SymbolToken{{Text: newString("$ion"), LocalSID: 1}}, false, false)
 	_nextA(t, r, nil, true, true)
 }
 
@@ -105,11 +105,11 @@ func TestReadTextFieldNames(t *testing.T) {
 	assert.NoError(t, r.StepIn())
 
 	_nextF(t, r, &SymbolToken{Text: nil, LocalSID: 0}, false, false)
-	_nextF(t, r, &SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown}, false, false)
+	_nextF(t, r, newSymbolTokenPtrFromString("$4"), false, false)
 	_nextF(t, r, &SymbolToken{Text: newString("name"), LocalSID: 4}, false, false)
 	_nextF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: 10}, false, false)
 	_nextF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: 10}, false, false)
-	_nextF(t, r, &SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}, false, false)
+	_nextF(t, r, newSymbolTokenPtrFromString("bar"), false, false)
 	_nextF(t, r, &SymbolToken{Text: newString("$ion"), LocalSID: 1}, false, false)
 	_nextF(t, r, &SymbolToken{}, true, true)
 }
@@ -132,7 +132,7 @@ func TestIgnoreValues(t *testing.T) {
 	_next(t, r, StructType)
 	_next(t, r, ListType)
 
-	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, NewSymbolTokenFromString("foo"))
 	_eof(t, r)
 }
 
@@ -151,13 +151,13 @@ func TestReadSexps(t *testing.T) {
 	})
 
 	test("(foo)", func(t *testing.T, r Reader) {
-		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, NewSymbolTokenFromString("foo"))
 	})
 
 	test("(foo bar baz :: boop)", func(t *testing.T, r Reader) {
-		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
-		_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-		_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("boop"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbol(t, r, NewSymbolTokenFromString("foo"))
+		_symbol(t, r, NewSymbolTokenFromString("bar"))
+		_symbolAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("baz")}, newSymbolTokenPtrFromString("boop"), false, false)
 	})
 }
 
@@ -175,17 +175,17 @@ func TestStructs(t *testing.T) {
 	})
 
 	test("{foo : bar :: baz}", func(t *testing.T, r Reader) {
-		_symbolAF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbolAF(t, r, newSymbolTokenPtrFromString("foo"), []SymbolToken{NewSymbolTokenFromString("bar")}, newSymbolTokenPtrFromString("baz"), false, false)
 	})
 
 	test("{foo: a, bar: b, baz: c}", func(t *testing.T, r Reader) {
-		_symbolAF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, false, false)
-		_symbolAF(t, r, &SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}, false, false)
-		_symbolAF(t, r, &SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("c"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbolAF(t, r, newSymbolTokenPtrFromString("foo"), nil, newSymbolTokenPtrFromString("a"), false, false)
+		_symbolAF(t, r, newSymbolTokenPtrFromString("bar"), nil, newSymbolTokenPtrFromString("b"), false, false)
+		_symbolAF(t, r, newSymbolTokenPtrFromString("baz"), nil, newSymbolTokenPtrFromString("c"), false, false)
 	})
 
 	test("{\"\": a}", func(t *testing.T, r Reader) {
-		_symbolAF(t, r, &SymbolToken{Text: newString(""), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbolAF(t, r, newSymbolTokenPtrFromString(""), nil, newSymbolTokenPtrFromString("a"), false, false)
 	})
 }
 
@@ -205,7 +205,7 @@ func TestNullStructs(t *testing.T) {
 	r := NewReaderString("null.struct 'null'::{foo:bar}")
 
 	_null(t, r, StructType)
-	_nextAF(t, r, StructType, nil, []SymbolToken{SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown}})
+	_nextAF(t, r, StructType, nil, []SymbolToken{NewSymbolTokenFromString("null")})
 	_eof(t, r)
 }
 
@@ -223,14 +223,14 @@ func TestLists(t *testing.T) {
 	})
 
 	test("[foo]", func(t *testing.T, r Reader) {
-		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, NewSymbolTokenFromString("foo"))
 		_eof(t, r)
 	})
 
 	test("[foo, bar, baz::boop]", func(t *testing.T, r Reader) {
-		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
-		_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-		_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("boop"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbol(t, r, NewSymbolTokenFromString("foo"))
+		_symbol(t, r, NewSymbolTokenFromString("bar"))
+		_symbolAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("baz")}, newSymbolTokenPtrFromString("boop"), false, false)
 		_eof(t, r)
 	})
 }
@@ -330,7 +330,7 @@ func TestTimestamps(t *testing.T) {
 	et2 := time.Date(2001, time.January, 1, 0, 0, 0, 1, time.UTC)
 	test("2001-01-01T00:00:00.000000000999Z", NewTimestampWithFractionalSeconds(et2, TimestampPrecisionNanosecond, TimezoneUTC, 12))
 
-	testA("foo::'bar'::2001-01-01T00:00:00.000Z", []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, NewTimestampWithFractionalSeconds(et, TimestampPrecisionNanosecond, TimezoneUTC, 3))
+	testA("foo::'bar'::2001-01-01T00:00:00.000Z", []SymbolToken{NewSymbolTokenFromString("foo"), NewSymbolTokenFromString("bar")}, NewTimestampWithFractionalSeconds(et, TimestampPrecisionNanosecond, TimezoneUTC, 3))
 }
 
 func TestDecimals(t *testing.T) {
@@ -361,7 +361,7 @@ func TestDecimals(t *testing.T) {
 	test("123d+2", "12300")
 	test("123d-2", "1.23")
 
-	testA("  foo :: 'bar' :: 123.  ", []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, "123")
+	testA("  foo :: 'bar' :: 123.  ", []SymbolToken{NewSymbolTokenFromString("foo"), NewSymbolTokenFromString("bar")}, "123")
 }
 
 func TestFloats(t *testing.T) {
@@ -383,7 +383,7 @@ func TestFloats(t *testing.T) {
 	test("+inf", math.Inf(1))
 	test("-inf", math.Inf(-1))
 
-	testA("foo::'bar'::1e100", []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, 1e100)
+	testA("foo::'bar'::1e100", []SymbolToken{NewSymbolTokenFromString("foo"), NewSymbolTokenFromString("bar")}, 1e100)
 }
 
 func TestInts(t *testing.T) {
@@ -447,9 +447,9 @@ func TestInts(t *testing.T) {
 func TestStrings(t *testing.T) {
 	r := NewReaderString(`foo::"bar" "baz" 'a'::'b'::'''beep''' '''boop''' null.string`)
 
-	_stringAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}}, newString("bar"))
+	_stringAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("foo")}, newString("bar"))
 	_string(t, r, newString("baz"))
-	_stringAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}}, newString("beepboop"))
+	_stringAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("a"), NewSymbolTokenFromString("b")}, newString("beepboop"))
 	_null(t, r, StringType)
 
 	_eof(t, r)
@@ -458,9 +458,9 @@ func TestStrings(t *testing.T) {
 func TestSymbols(t *testing.T) {
 	r := NewReaderString("'null'::foo bar a::b::'baz' null.symbol")
 
-	_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, false, false)
-	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-	_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, false, false)
+	_symbolAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("null")}, newSymbolTokenPtrFromString("foo"), false, false)
+	_symbol(t, r, NewSymbolTokenFromString("bar"))
+	_symbolAF(t, r, nil, []SymbolToken{NewSymbolTokenFromString("a"), NewSymbolTokenFromString("b")}, newSymbolTokenPtrFromString("baz"), false, false)
 	_null(t, r, SymbolType)
 
 	_eof(t, r)
@@ -482,12 +482,12 @@ func TestOperators(t *testing.T) {
 	r := NewReaderString("(a*(b+c))")
 
 	_sexp(t, r, func(t *testing.T, r Reader) {
-		_symbol(t, r, SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
-		_symbol(t, r, SymbolToken{Text: newString("*"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, NewSymbolTokenFromString("a"))
+		_symbol(t, r, NewSymbolTokenFromString("*"))
 		_sexp(t, r, func(t *testing.T, r Reader) {
-			_symbol(t, r, SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown})
-			_symbol(t, r, SymbolToken{Text: newString("+"), LocalSID: SymbolIDUnknown})
-			_symbol(t, r, SymbolToken{Text: newString("c"), LocalSID: SymbolIDUnknown})
+			_symbol(t, r, NewSymbolTokenFromString("b"))
+			_symbol(t, r, NewSymbolTokenFromString("+"))
+			_symbol(t, r, NewSymbolTokenFromString("c"))
 			_eof(t, r)
 		})
 		_eof(t, r)
@@ -497,7 +497,7 @@ func TestOperators(t *testing.T) {
 func TestTopLevelOperators(t *testing.T) {
 	r := NewReaderString("a + b")
 
-	_symbol(t, r, SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, NewSymbolTokenFromString("a"))
 
 	assert.False(t, r.Next())
 	assert.Error(t, r.Err())
diff --git a/ion/textutils_test.go b/ion/textutils_test.go
index 735e9f2..eeddda7 100644
--- a/ion/textutils_test.go
+++ b/ion/textutils_test.go
@@ -73,7 +73,7 @@ func TestWriteSymbol(t *testing.T) {
 	test := func(sym, expected string) {
 		t.Run(expected, func(t *testing.T) {
 			buf := strings.Builder{}
-			require.NoError(t, writeSymbol(sym, &buf))
+			require.NoError(t, writeSymbolFromString(sym, &buf))
 			actual := buf.String()
 			assert.Equal(t, expected, actual)
 		})
diff --git a/ion/textwriter_test.go b/ion/textwriter_test.go
index a4ad161..b184dd3 100644
--- a/ion/textwriter_test.go
+++ b/ion/textwriter_test.go
@@ -28,7 +28,7 @@ import (
 
 func TestWriteTextTopLevelFieldName(t *testing.T) {
 	writeText(func(w Writer) {
-		assert.Error(t, w.FieldName("foo"))
+		assert.Error(t, w.FieldName(NewSymbolTokenFromString("foo")))
 	})
 }
 
@@ -44,9 +44,9 @@ func TestWriteTextEmptyStruct(t *testing.T) {
 
 func TestWriteTextAnnotatedStruct(t *testing.T) {
 	testTextWriter(t, "foo::$bar::'.baz'::{}", func(w Writer) {
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("$bar"), LocalSID: SymbolIDUnknown}))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString(".baz"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("foo")))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("$bar")))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString(".baz")))
 		assert.NoError(t, w.BeginStruct())
 		require.NoError(t, w.EndStruct())
 	})
@@ -56,12 +56,12 @@ func TestWriteTextNestedStruct(t *testing.T) {
 	testTextWriter(t, "{foo:'true'::{},'null':{}}", func(w Writer) {
 		assert.NoError(t, w.BeginStruct())
 
-		assert.NoError(t, w.FieldName("foo"))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("true"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("foo")))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("true")))
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
 
-		assert.NoError(t, w.FieldName("null"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("null")))
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
 
@@ -84,11 +84,11 @@ func TestWriteTextNestedLists(t *testing.T) {
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("foo")))
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("null")))
 		assert.NoError(t, w.BeginList())
 		assert.NoError(t, w.EndList())
 
@@ -124,7 +124,7 @@ func TestWriteTextNulls(t *testing.T) {
 		assert.NoError(t, w.BeginList())
 
 		assert.NoError(t, w.WriteNull())
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("foo")))
 		assert.NoError(t, w.WriteNullType(NullType))
 		assert.NoError(t, w.WriteNullType(BoolType))
 		assert.NoError(t, w.WriteNullType(IntType))
@@ -136,7 +136,7 @@ func TestWriteTextNulls(t *testing.T) {
 		assert.NoError(t, w.WriteNullType(ClobType))
 		assert.NoError(t, w.WriteNullType(BlobType))
 		assert.NoError(t, w.WriteNullType(ListType))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("null")))
 		assert.NoError(t, w.WriteNullType(SexpType))
 		assert.NoError(t, w.WriteNullType(StructType))
 
@@ -152,12 +152,12 @@ func TestWriteTextBool(t *testing.T) {
 		assert.NoError(t, w.BeginSexp())
 
 		assert.NoError(t, w.WriteBool(false))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("123"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("123")))
 		assert.NoError(t, w.WriteBool(true))
 
 		assert.NoError(t, w.EndSexp())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("false"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("false")))
 		assert.NoError(t, w.WriteBool(false))
 	})
 }
@@ -167,7 +167,7 @@ func TestWriteTextInt(t *testing.T) {
 	testTextWriter(t, expected, func(w Writer) {
 		assert.NoError(t, w.BeginSexp())
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("zero"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("zero")))
 		assert.NoError(t, w.WriteInt(0))
 		assert.NoError(t, w.WriteInt(1))
 		assert.NoError(t, w.WriteInt(-1))
@@ -193,7 +193,7 @@ func TestWriteTextBigInt(t *testing.T) {
 		one.SetInt64(1)
 		val.Add(&max, &one)
 
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("big"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("big")))
 		assert.NoError(t, w.WriteBigInt(&val))
 
 		assert.NoError(t, w.EndList())
@@ -205,21 +205,21 @@ func TestWriteTextFloat(t *testing.T) {
 	testTextWriter(t, expected, func(w Writer) {
 		assert.NoError(t, w.BeginStruct())
 
-		assert.NoError(t, w.FieldName("z"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("z")))
 		assert.NoError(t, w.WriteFloat(0.0))
-		assert.NoError(t, w.FieldName("nz"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("nz")))
 		assert.NoError(t, w.WriteFloat(-1.0/math.Inf(1)))
 
-		assert.NoError(t, w.FieldName("s"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("s")))
 		assert.NoError(t, w.WriteFloat(12.34))
-		assert.NoError(t, w.FieldName("l"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("l")))
 		assert.NoError(t, w.WriteFloat(12.34e-56))
 
-		assert.NoError(t, w.FieldName("n"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("n")))
 		assert.NoError(t, w.WriteFloat(math.NaN()))
-		assert.NoError(t, w.FieldName("i"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("i")))
 		assert.NoError(t, w.WriteFloat(math.Inf(1)))
-		assert.NoError(t, w.FieldName("ni"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("ni")))
 		assert.NoError(t, w.WriteFloat(math.Inf(-1)))
 
 		assert.NoError(t, w.EndStruct())
@@ -245,25 +245,25 @@ func TestWriteTextTimestamp(t *testing.T) {
 }
 
 func TestWriteTextSymbol(t *testing.T) {
-	expected := "{foo:bar,empty:'','null':'null',f:a::b::u::'lo🇺🇸',$123:$456}"
+	expected := "{foo:bar,empty:'','null':'null',f:a::b::u::'lo🇺🇸','$123':'$456'}"
 	testTextWriter(t, expected, func(w Writer) {
 		assert.NoError(t, w.BeginStruct())
 
-		assert.NoError(t, w.FieldName("foo"))
-		assert.NoError(t, w.WriteSymbol("bar"))
-		assert.NoError(t, w.FieldName("empty"))
-		assert.NoError(t, w.WriteSymbol(""))
-		assert.NoError(t, w.FieldName("null"))
-		assert.NoError(t, w.WriteSymbol("null"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("foo")))
+		assert.NoError(t, w.WriteSymbolFromString("bar"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("empty")))
+		assert.NoError(t, w.WriteSymbolFromString(""))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("null")))
+		assert.NoError(t, w.WriteSymbolFromString("null"))
 
-		assert.NoError(t, w.FieldName("f"))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("u"), LocalSID: SymbolIDUnknown}))
-		assert.NoError(t, w.WriteSymbol("lo🇺🇸"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("f")))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("a")))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("b")))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("u")))
+		assert.NoError(t, w.WriteSymbolFromString("lo🇺🇸"))
 
-		assert.NoError(t, w.FieldName("$123"))
-		assert.NoError(t, w.WriteSymbol("$456"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("$123")))
+		assert.NoError(t, w.WriteSymbol(NewSymbolTokenFromString("$456")))
 
 		assert.NoError(t, w.EndStruct())
 	})
@@ -278,7 +278,7 @@ func TestWriteTextString(t *testing.T) {
 
 		assert.NoError(t, w.BeginSexp())
 		assert.NoError(t, w.WriteString("\\\"\n\"\\"))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("zany"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("zany")))
 		assert.NoError(t, w.WriteString("🤪"))
 		assert.NoError(t, w.EndSexp())
 
@@ -291,7 +291,7 @@ func TestWriteTextBlob(t *testing.T) {
 	testTextWriter(t, expected, func(w Writer) {
 		assert.NoError(t, w.WriteBlob([]byte{0, 1, 2, 0xFD, 0xFE, 0xFF}))
 		assert.NoError(t, w.WriteBlob([]byte("Hello World")))
-		assert.NoError(t, w.Annotation(SymbolToken{Text: newString("empty"), LocalSID: SymbolIDUnknown}))
+		assert.NoError(t, w.Annotation(NewSymbolTokenFromString("empty")))
 		assert.NoError(t, w.WriteBlob(nil))
 	})
 }
@@ -300,9 +300,9 @@ func TestWriteTextClob(t *testing.T) {
 	expected := "{hello:{{\"world\"}},bits:{{\"\\0\\x01\\xFE\\xFF\"}}}"
 	testTextWriter(t, expected, func(w Writer) {
 		assert.NoError(t, w.BeginStruct())
-		assert.NoError(t, w.FieldName("hello"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("hello")))
 		assert.NoError(t, w.WriteClob([]byte("world")))
-		assert.NoError(t, w.FieldName("bits"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("bits")))
 		assert.NoError(t, w.WriteClob([]byte{0, 1, 0xFE, 0xFF}))
 		assert.NoError(t, w.EndStruct())
 	})
@@ -312,7 +312,7 @@ func TestWriteTextFinish(t *testing.T) {
 	expected := "1\nfoo\n\"bar\"\n{}\n"
 	testTextWriter(t, expected, func(w Writer) {
 		assert.NoError(t, w.WriteInt(1))
-		assert.NoError(t, w.WriteSymbol("foo"))
+		assert.NoError(t, w.WriteSymbolFromString("foo"))
 		assert.NoError(t, w.WriteString("bar"))
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
@@ -334,39 +334,39 @@ func TestWriteTextPretty(t *testing.T) {
 
 	assert.NoError(t, w.BeginStruct())
 	{
-		assert.NoError(t, w.FieldName("struct"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("struct")))
 		assert.NoError(t, w.BeginStruct())
 		assert.NoError(t, w.EndStruct())
 
-		assert.NoError(t, w.FieldName("list"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("list")))
 		assert.NoError(t, w.Annotations(
-			SymbolToken{Text: newString("i"), LocalSID: SymbolIDUnknown},
-			SymbolToken{Text: newString("am"), LocalSID: SymbolIDUnknown},
-			SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown},
-			SymbolToken{Text: newString("list"), LocalSID: SymbolIDUnknown}))
+			NewSymbolTokenFromString("i"),
+			NewSymbolTokenFromString("am"),
+			NewSymbolTokenFromString("a"),
+			NewSymbolTokenFromString("list")))
 		assert.NoError(t, w.BeginList())
 		{
 			assert.NoError(t, w.WriteString("value"))
 			assert.NoError(t, w.WriteNullType(StringType))
 			assert.NoError(t, w.BeginStruct())
 			{
-				assert.NoError(t, w.FieldName("1"))
+				assert.NoError(t, w.FieldName(NewSymbolTokenFromString("1")))
 				assert.NoError(t, w.WriteString("one"))
-				assert.NoError(t, w.FieldName("2"))
+				assert.NoError(t, w.FieldName(NewSymbolTokenFromString("2")))
 				assert.NoError(t, w.WriteString("two"))
 			}
 			assert.NoError(t, w.EndStruct())
 		}
 		assert.NoError(t, w.EndList())
 
-		assert.NoError(t, w.FieldName("sexp"))
+		assert.NoError(t, w.FieldName(NewSymbolTokenFromString("sexp")))
 		assert.NoError(t, w.BeginSexp())
 		{
-			assert.NoError(t, w.WriteSymbol("+"))
+			assert.NoError(t, w.WriteSymbolFromString("+"))
 			assert.NoError(t, w.WriteInt(123))
 			assert.NoError(t, w.BeginSexp())
 			{
-				assert.NoError(t, w.WriteSymbol("*"))
+				assert.NoError(t, w.WriteSymbolFromString("*"))
 				assert.NoError(t, w.WriteInt(456))
 				assert.NoError(t, w.WriteInt(789))
 			}
diff --git a/ion/unmarshal_test.go b/ion/unmarshal_test.go
index 556b488..a3612ff 100644
--- a/ion/unmarshal_test.go
+++ b/ion/unmarshal_test.go
@@ -336,6 +336,40 @@ func TestUnmarshalListSexpBinary(t *testing.T) {
 	test(ionByteValue, "sexpToSlice", &[]int{}, &[]int{2, 3, 4})                     // unmarshal IonSexp to Slice of int
 }
 
+func TestRoundtripMarshalUnmarshalWithSymbolTable(t *testing.T) {
+	sst := NewSharedSymbolTable("shared symbol table", 1, []string{
+		"FirstName",
+		"LastName",
+	})
+
+	test := func(testName string, expectedFirstName, expectedLastName string, expectedAge int) {
+		t.Run(testName, func(t *testing.T) {
+			goStruct := map[string]interface{}{
+				"FirstName": expectedFirstName,
+				"LastName":  expectedLastName,
+				"Age":       expectedAge,
+			}
+
+			ionBinary, err := MarshalBinary(goStruct, sst)
+			require.NoError(t, err)
+
+			var decodedResult map[string]interface{}
+			require.NoError(t, Unmarshal(ionBinary, &decodedResult, sst))
+
+			decodedFirstName := decodedResult["FirstName"].(string)
+			decodedLastName := decodedResult["LastName"].(string)
+			decodedAge := decodedResult["Age"].(int)
+
+			assert.Equal(t, expectedFirstName, decodedFirstName)
+			assert.Equal(t, expectedLastName, decodedLastName)
+			assert.Equal(t, expectedAge, decodedAge)
+		})
+	}
+
+	test("John Doe", "John", "Doe", 26)
+	test("Mary Thompson", "Mary", "Thompson", 33)
+}
+
 func TestDecodeFloat(t *testing.T) {
 	test32 := func(str string, eval float32) {
 		t.Run(str, func(t *testing.T) {
@@ -541,7 +575,7 @@ func TestDecode(t *testing.T) {
 
 	test("2020T", NewDateTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), TimestampPrecisionYear))
 
-	test("hello", &SymbolToken{Text: newString("hello"), LocalSID: SymbolIDUnknown})
+	test("hello", newSymbolTokenPtrFromString("hello"))
 	test("\"hello\"", newString("hello"))
 
 	test("null.blob", nil)
@@ -556,14 +590,14 @@ func TestDecode(t *testing.T) {
 	test("{}", map[string]interface{}{})
 	test("{a:1,b:two}", map[string]interface{}{
 		"a": 1,
-		"b": &SymbolToken{Text: newString("two"), LocalSID: SymbolIDUnknown},
+		"b": newSymbolTokenPtrFromString("two"),
 	})
 
 	test("null.list", nil)
-	test("[1, two]", []interface{}{1, &SymbolToken{Text: newString("two"), LocalSID: SymbolIDUnknown}})
+	test("[1, two]", []interface{}{1, newSymbolTokenPtrFromString("two")})
 
 	test("null.sexp", nil)
-	test("(1 + two)", []interface{}{1, &SymbolToken{Text: newString("+"), LocalSID: SymbolIDUnknown}, &SymbolToken{Text: newString("two"), LocalSID: SymbolIDUnknown}})
+	test("(1 + two)", []interface{}{1, newSymbolTokenPtrFromString("+"), newSymbolTokenPtrFromString("two")})
 
 	var result []interface{}
 	test("()", result)
@@ -677,14 +711,14 @@ func TestUnmarshalNestedStructsWithAnnotation(t *testing.T) {
 		  }
 		}
 	*/
-	innerStructVal := nestedInt{Value: 5, ValueAnnotation: []SymbolToken{SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}}}
-	mainStructVal := nestedStruct{Field2: innerStructVal, InnerStructAnnotation: []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}}
-	expectedValue := topLevelStruct{Field1: mainStructVal, TopLevelAnnotation: []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}}}
+	innerStructVal := nestedInt{Value: 5, ValueAnnotation: []SymbolToken{NewSymbolTokenFromString("baz")}}
+	mainStructVal := nestedStruct{Field2: innerStructVal, InnerStructAnnotation: []SymbolToken{NewSymbolTokenFromString("bar")}}
+	expectedValue := topLevelStruct{Field1: mainStructVal, TopLevelAnnotation: []SymbolToken{NewSymbolTokenFromString("foo")}}
 
 	test("foo::{Field1:bar::{Field2:baz::5}}", "nested structs", expectedValue)
 }
 
-var symbolTokenWith = SymbolToken{Text: newString("with"), LocalSID: SymbolIDUnknown}
-var symbolTokenMultiple = SymbolToken{Text: newString("multiple"), LocalSID: SymbolIDUnknown}
-var symbolTokenAnnotations = SymbolToken{Text: newString("annotations"), LocalSID: SymbolIDUnknown}
+var symbolTokenWith = NewSymbolTokenFromString("with")
+var symbolTokenMultiple = NewSymbolTokenFromString("multiple")
+var symbolTokenAnnotations = NewSymbolTokenFromString("annotations")
 var annotations = []SymbolToken{symbolTokenWith, symbolTokenMultiple, symbolTokenAnnotations}