Repository: qmuntal/gltf
Pull request #6 (2019-03-25 11:14:22)

Improve default property handling and extensions definition


Patch:
diff --git a/README.md b/README.md
index 1957174..50b1bb8 100644
--- a/README.md
+++ b/README.md
@@ -66,11 +66,11 @@ if err := gltf.Save(doc, "./a.gltf", true); err != nil {
 ```go
 doc := &gltf.Document{
   Accessors: []gltf.Accessor{
-    {BufferView: 0, ByteOffset: 0, ComponentType: gltf.UnsignedShort, Count: 36, Type: gltf.Scalar},
-    {BufferView: 1, ByteOffset: 0, ComponentType: gltf.Float, Count: 24, Max: []float64{0.5, 0.5, 0.5}, Min: []float64{-0.5, -0.5, -0.5}, Type: gltf.Vec3},
-    {BufferView: 2, ByteOffset: 0, ComponentType: gltf.Float, Count: 24, Type: gltf.Vec3},
-    {BufferView: 3, ByteOffset: 0, ComponentType: gltf.Float, Count: 24, Type: gltf.Vec4},
-    {BufferView: 4, ByteOffset: 0, ComponentType: gltf.Float, Count: 24, Type: gltf.Vec2},
+    {BufferView: gltf.Index(0), ComponentType: gltf.UnsignedShort, Count: 36, Type: gltf.Scalar},
+    {BufferView: gltf.Index(1), ComponentType: gltf.Float, Count: 24, Max: []float64{0.5, 0.5, 0.5}, Min: []float64{-0.5, -0.5, -0.5}, Type: gltf.Vec3},
+    {BufferView: gltf.Index(2), ComponentType: gltf.Float, Count: 24, Type: gltf.Vec3},
+    {BufferView: gltf.Index(3), ComponentType: gltf.Float, Count: 24, Type: gltf.Vec4},
+    {BufferView: gltf.Index(4), ComponentType: gltf.Float, Count: 24, Type: gltf.Vec2},
   },
   Asset: gltf.Asset{Version: "2.0", Generator: "FBX2glTF"},
   BufferViews: []gltf.BufferView{
@@ -81,18 +81,18 @@ doc := &gltf.Document{
     {Buffer: 0, ByteLength: 192, ByteOffset: 1032, Target: gltf.ArrayBuffer},
   },
   Buffers: []gltf.Buffer{{ByteLength: 1224, Data: readFile("testdata/BoxVertexColors/glTF-Binary/BoxVertexColors.glb")[1628+20+8:]}},
-  Materials: []gltf.Material{{Name: "Default", AlphaMode: gltf.Opaque, AlphaCutoff: 0.5, 
-    PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: [4]float64{0.8, 0.8, 0.8, 1}, MetallicFactor: 0.1, RoughnessFactor: 0.99}}
+  Materials: []gltf.Material{{Name: "Default", AlphaMode: gltf.Opaque, AlphaCutoff: gltf.Float64(0.5), 
+    PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: [4]float64{0.8, 0.8, 0.8, 1}, MetallicFactor: gltf.Float64(0.1), RoughnessFactor: gltf.Float64(0.99)}}
   },
-  Meshes: []gltf.Mesh{{Name: "Cube", Primitives: []gltf.Primitive{{Indices: 0, Material: 0, Mode: gltf.Triangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}},
+  Meshes: []gltf.Mesh{{Name: "Cube", Primitives: []gltf.Primitive{{Indices: gltf.Index(0), Material: gltf.Index(0), Mode: gltf.Triangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}},
   Nodes: []gltf.Node{
-    {Name: "RootNode", Mesh: -1, Camera: -1, Skin: -1, Children: []uint32{1, 2, 3}, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
-    {Name: "Mesh", Mesh: -1, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
-    {Name: "Cube", Mesh: 0, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
-    {Name: "Texture Group", Mesh: -1, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
+    {Name: "RootNode", Children: []uint32{1, 2, 3}},
+    {Name: "Mesh"},
+    {Name: "Cube", Mesh: gltf.Index(0)},
+    {Name: "Texture Group"},
   },
   Samplers: []gltf.Sampler{{WrapS: gltf.Repeat, WrapT: gltf.Repeat}},
-  Scene: 0,
+  Scene: gltf.Index(0),
   Scenes: []gltf.Scene{{Name: "Root Scene", Nodes: []uint32{0}}},
 }
 if err := gltf.Save(doc, "./a.gltf", true); err != nil {
diff --git a/const.go b/const.go
index 6d73a9f..0f2e47d 100644
--- a/const.go
+++ b/const.go
@@ -1,65 +1,135 @@
 package gltf
 
+import "encoding/json"
+
+var (
+	// DefaultMatrix defines an identity matrix.
+	DefaultMatrix = [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}
+	// DefaultRotation defines a quaternion without rotation.
+	DefaultRotation = [4]float64{0, 0, 0, 1}
+	// DefaultScale defines a scaling that does not modify the size of the object.
+	DefaultScale = [3]float64{1, 1, 1}
+	// DefaultTranslation defines a translation that does not move the object.
+	DefaultTranslation = [3]float64{0, 0, 0}
+)
+
+var (
+	emptyMatrix   = [16]float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+	emptyRotation = [4]float64{0, 0, 0, 0}
+	emptyScale    = [3]float64{0, 0, 0}
+)
+
 // The ComponentType is the datatype of components in the attribute. All valid values correspond to WebGL enums.
 // 5125 (UNSIGNED_INT) is only allowed when the accessor contains indices.
 type ComponentType uint16
 
 const (
+	// Float corresponds to a Float32Array.
+	Float ComponentType = iota
 	// Byte corresponds to a Int8Array.
-	Byte ComponentType = 5120
+	Byte
 	// UnsignedByte corresponds to a Uint8Array.
-	UnsignedByte = 5121
+	UnsignedByte
 	// Short corresponds to a Int16Array.
-	Short = 5122
+	Short
 	// UnsignedShort corresponds to a Uint16Array.
-	UnsignedShort = 5123
+	UnsignedShort
 	// UnsignedInt corresponds to a Uint32Array.
-	UnsignedInt = 5125
-	// Float corresponds to a Float32Array.
-	Float = 5126
+	UnsignedInt
 )
 
+// UnmarshalJSON unmarshal the component type with the correct default values.
+func (c *ComponentType) UnmarshalJSON(data []byte) error {
+	var tmp uint16
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*c = map[uint16]ComponentType{
+			5120: Byte,
+			5121: UnsignedByte,
+			5122: Short,
+			5123: UnsignedShort,
+			5125: UnsignedInt,
+			5126: Float,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the component type with the correct default values.
+func (c *ComponentType) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[ComponentType]uint16{
+		Byte:          5120,
+		UnsignedByte:  5121,
+		Short:         5122,
+		UnsignedShort: 5123,
+		UnsignedInt:   5125,
+		Float:         5126,
+	}[*c])
+}
+
 // AccessorType specifies if the attribute is a scalar, vector, or matrix.
-type AccessorType string
+type AccessorType uint8
 
 const (
 	// Scalar corresponds to a single dimension value.
-	Scalar AccessorType = "SCALAR"
+	Scalar AccessorType = iota
 	// Vec2 corresponds to a two dimensions array.
-	Vec2 = "VEC2"
+	Vec2
 	// Vec3 corresponds to a three dimensions array.
-	Vec3 = "VEC3"
+	Vec3
 	// Vec4 corresponds to a four dimensions array.
-	Vec4 = "VEC4"
+	Vec4
 	// Mat2 corresponds to a 2x2 matrix.
-	Mat2 = "MAT2"
+	Mat2
 	// Mat3 corresponds to a 3x3 matrix.
-	Mat3 = "MAT3"
+	Mat3
 	// Mat4 corresponds to a 4x4 matrix.
-	Mat4 = "MAT4"
+	Mat4
 )
 
+// UnmarshalJSON unmarshal the accessor type with the correct default values.
+func (a *AccessorType) UnmarshalJSON(data []byte) error {
+	var tmp string
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*a = map[string]AccessorType{
+			"SCALAR": Scalar,
+			"VEC2":   Vec2,
+			"VEC3":   Vec3,
+			"VEC4":   Vec4,
+			"MAT2":   Mat2,
+			"MAT3":   Mat3,
+			"MAT4":   Mat4,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the accessor type with the correct default values.
+func (a *AccessorType) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[AccessorType]string{
+		Scalar: "SCALAR",
+		Vec2:   "VEC2",
+		Vec3:   "VEC3",
+		Vec4:   "VEC4",
+		Mat2:   "MAT2",
+		Mat3:   "MAT3",
+		Mat4:   "MAT4",
+	}[*a])
+}
+
 // The Target that the GPU buffer should be bound to.
 type Target uint16
 
 const (
+	// None is used when the buffer should not bound to a target, for example when referenced by an sparce indices.
+	None = 0
 	// ArrayBuffer corresponds to an array buffer.
 	ArrayBuffer Target = 34962
 	// ElementArrayBuffer corresponds to an element array buffer.
 	ElementArrayBuffer = 34963
 )
 
-// CameraType specifies if the camera uses a perspective or orthographic projection.
-// Based on this, either the camera's perspective or orthographic property will be defined.
-type CameraType string
-
-const (
-	// PerspectiveType corresponds to a perspective camera.
-	PerspectiveType CameraType = "perspective"
-	// OrthographicType corresponds to an orthographic camera.
-	OrthographicType = "orthographic"
-)
-
 // Attribute is a map that each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data.
 type Attribute = map[string]uint32
 
@@ -67,101 +137,276 @@ type Attribute = map[string]uint32
 type PrimitiveMode uint8
 
 const (
+	// Triangles corresponds to a Triangle primitive.
+	Triangles PrimitiveMode = iota
 	// Points corresponds to a Point primitive.
-	Points PrimitiveMode = 0
+	Points
 	// Lines corresponds to a Line primitive.
-	Lines = 1
+	Lines
 	// LineLoop corresponds to a Line Loop primitive.
-	LineLoop = 2
+	LineLoop
 	// LineStrip corresponds to a Line Strip primitive.
-	LineStrip = 3
-	// Triangles corresponds to a Triangle primitive.
-	Triangles = 4
+	LineStrip
 	// TriangleStrip corresponds to a Triangle Strip primitive.
-	TriangleStrip = 5
+	TriangleStrip
 	// TriangleFan corresponds to a Triangle Fan primitive.
-	TriangleFan = 6
+	TriangleFan
 )
 
+// UnmarshalJSON unmarshal the primitive mode with the correct default values.
+func (p *PrimitiveMode) UnmarshalJSON(data []byte) error {
+	var tmp uint8
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*p = map[uint8]PrimitiveMode{
+			0: Points,
+			1: Lines,
+			2: LineLoop,
+			3: LineStrip,
+			4: Triangles,
+			5: TriangleStrip,
+			6: TriangleFan,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the primitive mode with the correct default values.
+func (p *PrimitiveMode) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[PrimitiveMode]uint8{
+		Points:        0,
+		Lines:         1,
+		LineLoop:      2,
+		LineStrip:     3,
+		Triangles:     4,
+		TriangleStrip: 5,
+		TriangleFan:   6,
+	}[*p])
+}
+
 // The AlphaMode enumeration specifying the interpretation of the alpha value of the main factor and texture.
-type AlphaMode string
+type AlphaMode uint8
 
 const (
 	// Opaque corresponds to an Opaque material.
-	Opaque AlphaMode = "OPAQUE"
+	Opaque AlphaMode = iota
 	// Mask corresponds to a masked material.
-	Mask = "MASK"
+	Mask
 	// Blend corresponds to a Blend material.
-	Blend = "BLEND"
+	Blend
 )
 
+// UnmarshalJSON unmarshal the alpha mode with the correct default values.
+func (a *AlphaMode) UnmarshalJSON(data []byte) error {
+	var tmp string
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*a = map[string]AlphaMode{
+			"OPAQUE": Opaque,
+			"MASK":   Mask,
+			"BLEND":  Blend,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the alpha mode with the correct default values.
+func (a *AlphaMode) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[AlphaMode]string{
+		Opaque: "OPAQUE",
+		Mask:   "MASK",
+		Blend:  "BLEND",
+	}[*a])
+}
+
 // MagFilter is the magnification filter.
 type MagFilter uint16
 
 const (
-	// MagNearest corresponds to a nearest magnification filter.
-	MagNearest MagFilter = 9728
 	// MagLinear corresponds to a linear magnification filter.
-	MagLinear = 9729
+	MagLinear MagFilter = iota
+	// MagNearest corresponds to a nearest magnification filter.
+	MagNearest
 )
 
+// UnmarshalJSON unmarshal the mag filter with the correct default values.
+func (m *MagFilter) UnmarshalJSON(data []byte) error {
+	var tmp uint16
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*m = map[uint16]MagFilter{
+			9728: MagNearest,
+			9729: MagLinear,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the alpha mode with the correct default values.
+func (m *MagFilter) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[MagFilter]uint16{
+		MagNearest: 9728,
+		MagLinear:  9729,
+	}[*m])
+}
+
 // MinFilter is the minification filter.
 type MinFilter uint16
 
 const (
+	// MinNearestMipMapLinear corresponds to a nearest mipmap linear minification filter.
+	MinNearestMipMapLinear MinFilter = iota
 	// MinNearest corresponds to a nearest minification filter.
-	MinNearest MinFilter = 9728
+	MinNearest
 	// MinLinear corresponds to a linear minification filter.
-	MinLinear = 9729
+	MinLinear
 	// MinNearestMipMapNearest corresponds to a nearest mipmap nearest minification filter.
-	MinNearestMipMapNearest = 9984
+	MinNearestMipMapNearest
 	// MinLinearMipMapNearest corresponds to a linear mipmap nearest minification filter.
-	MinLinearMipMapNearest = 9985
-	// MinNearestMipMapLinear corresponds to a nearest mipmap linear minification filter.
-	MinNearestMipMapLinear = 9986
+	MinLinearMipMapNearest
 	// MinLinearMipMapLinear corresponds to a linear mipmap linear minification filter.
-	MinLinearMipMapLinear = 9987
+	MinLinearMipMapLinear
 )
 
+// UnmarshalJSON unmarshal the min filter with the correct default values.
+func (m *MinFilter) UnmarshalJSON(data []byte) error {
+	var tmp uint16
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*m = map[uint16]MinFilter{
+			9728: MinNearest,
+			9729: MinLinear,
+			9984: MinNearestMipMapNearest,
+			9985: MinLinearMipMapNearest,
+			9986: MinNearestMipMapLinear,
+			9987: MinLinearMipMapLinear,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the min filter with the correct default values.
+func (m *MinFilter) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[MinFilter]uint16{
+		MinNearest:              9728,
+		MinLinear:               9729,
+		MinNearestMipMapNearest: 9984,
+		MinLinearMipMapNearest:  9985,
+		MinNearestMipMapLinear:  9986,
+		MinLinearMipMapLinear:   9987,
+	}[*m])
+}
+
 // WrappingMode is the wrapping mode of a texture.
 type WrappingMode uint16
 
 const (
+	// Repeat corresponds to a repeat wrapping.
+	Repeat WrappingMode = iota
 	// ClampToEdge corresponds to a clamp to edge wrapping.
-	ClampToEdge WrappingMode = 33071
+	ClampToEdge
 	// MirroredRepeat corresponds to a mirrored repeat wrapping.
-	MirroredRepeat = 33648
-	// Repeat corresponds to a repeat wrapping.
-	Repeat = 10497
+	MirroredRepeat
 )
 
+// UnmarshalJSON unmarshal the wrapping mode with the correct default values.
+func (w *WrappingMode) UnmarshalJSON(data []byte) error {
+	var tmp uint16
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*w = map[uint16]WrappingMode{
+			33071: ClampToEdge,
+			33648: MirroredRepeat,
+			10497: Repeat,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the wrapping mode with the correct default values.
+func (w *WrappingMode) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[WrappingMode]uint16{
+		ClampToEdge:    33071,
+		MirroredRepeat: 33648,
+		Repeat:         10497,
+	}[*w])
+}
+
 // Interpolation algorithm.
-type Interpolation string
+type Interpolation uint8
 
 const (
 	// Linear corresponds to a linear interpolation.
-	Linear Interpolation = "LINEAR"
+	Linear Interpolation = iota
 	// Step corresponds to a step interpolation.
-	Step = "STEP"
+	Step
 	// CubicSpline corresponds to a cubic spline interpolation.
-	CubicSpline = "CUBICSPLINE"
+	CubicSpline
 )
 
+// UnmarshalJSON unmarshal the interpolation with the correct default values.
+func (i *Interpolation) UnmarshalJSON(data []byte) error {
+	var tmp string
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*i = map[string]Interpolation{
+			"LINEAR":      Linear,
+			"STEP":        Step,
+			"CUBICSPLINE": CubicSpline,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the interpolation with the correct default values.
+func (i *Interpolation) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[Interpolation]string{
+		Linear:      "LINEAR",
+		Step:        "STEP",
+		CubicSpline: "CUBICSPLINE",
+	}[*i])
+}
+
 // TRSProperty defines a local space transformation.
 // TRSproperties are converted to matrices and postmultiplied in the T * R * S order to compose the transformation matrix.
-type TRSProperty string
+type TRSProperty uint8
 
 const (
 	// Translation corresponds to a translation transform.
-	Translation TRSProperty = "translation"
+	Translation TRSProperty = iota
 	// Rotation corresponds to a rotation transform.
-	Rotation = "rotation"
+	Rotation
 	// Scale corresponds to a scale transform.
-	Scale = "scale"
+	Scale
 	// Weights corresponds to a weights transform.
-	Weights = "weights"
+	Weights
 )
 
+// UnmarshalJSON unmarshal the TRSProperty with the correct default values.
+func (t *TRSProperty) UnmarshalJSON(data []byte) error {
+	var tmp string
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*t = map[string]TRSProperty{
+			"translation": Translation,
+			"rotation":    Rotation,
+			"scale":       Scale,
+			"weights":     Weights,
+		}[tmp]
+	}
+	return err
+}
+
+// MarshalJSON marshal the TRSProperty with the correct default values.
+func (t *TRSProperty) MarshalJSON() ([]byte, error) {
+	return json.Marshal(map[TRSProperty]string{
+		Translation: "translation",
+		Rotation:    "rotation",
+		Scale:       "scale",
+		Weights:     "weights",
+	}[*t])
+}
+
 const (
 	glbHeaderMagic = 0x46546c67
 	glbChunkJSON   = 0x4e4f534a
diff --git a/doc.go b/doc.go
index 1eda2e9..4eb0a74 100644
--- a/doc.go
+++ b/doc.go
@@ -1,6 +1,18 @@
+// Package gltf implements a glTF 2.0 file decoder, encoder and validator.
 /*
-Package gltf implements a glTF 2.0 file decoder, encoder and validator.
-
 The glTF 2.0 specification is at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0/.
+
+Optional Properties
+
+All optional properties whose default value does not match with the golang type zero value are defines as pointers or as zero arrays.
+
+Guidelines when working with optional values:
+* It is safe to not define them when saving the glTF if the desired value is the default one.
+* It is safe to expect that the optional values are not nil when opening a glTF.
+* When assigning values to optional properties one can use the utility functions that take the reference of basic types. Examples:
+ gltf.Index(1)
+ gltf.Float64(0.5)
+* When quering values of optional properties that are not indices it is recommended to use the utility functions that returns the property as value
+if not nil or the default value if nil.
 */
 package gltf
diff --git a/extensions.go b/extensions.go
deleted file mode 100644
index 4b01ec4..0000000
--- a/extensions.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package gltf
-
-import "encoding/json"
-
-const (
-	ExtPBRSpecularGlossiness = "KHR_materials_pbrSpecularGlossiness"
-)
-
-// Extension is map where the keys are the extension identifiers and the values are the extensions payloads.
-// If a key matches with one of the supported extensions the value will be marshalled as a pointer to the extension struct.
-// If a key does not match with any of the supported extensions the value will be a json.RawMessage so its decoding can be delayed.
-type Extensions map[string]interface{}
-
-type envelope map[string]json.RawMessage
-
-// UnmarshalJSON unmarshal the extensions with the supported extensions initialized.
-func (ext *Extensions) UnmarshalJSON(data []byte) error {
-	if len(*ext) == 0 {
-		*ext = make(Extensions)
-	}
-	var raw envelope
-	err := json.Unmarshal(data, &raw)
-	if err == nil {
-		for key, value := range raw {
-			switch key {
-			case ExtPBRSpecularGlossiness:
-				n := &PBRSpecularGlossiness{}
-				if err := json.Unmarshal(value, n); err != nil {
-					return err
-				}
-				(*ext)[ExtPBRSpecularGlossiness] = n
-			default:
-				(*ext)[key] = value
-			}
-		}
-	}
-
-	return err
-}
-
-type PBRSpecularGlossiness struct {
-	DiffuseFactor             [4]float64   `json:"diffuseFactor" validate:"dive,gte=0,lte=1"`
-	DiffuseTexture            *TextureInfo `json:"diffuseTexture,omitempty"`
-	SpecularFactor            [3]float64   `json:"specularFactor" validate:"dive,gte=0,lte=1"`
-	GlossinessFactor          float64      `json:"glossinessFactor" validate:"gte=0,lte=1"`
-	SpecularGlossinessTexture *TextureInfo `json:"specularGlossinessTexture,omitempty"`
-}
-
-// PBRSpecularGlossiness returns a default PBRSpecularGlossiness.
-func NewPBRSpecularGlossiness() *PBRSpecularGlossiness {
-	return &PBRSpecularGlossiness{DiffuseFactor: [4]float64{1, 1, 1, 1}, SpecularFactor: [3]float64{1, 1, 1}, GlossinessFactor: 1}
-}
-
-// UnmarshalJSON unmarshal the pbr with the correct default values.
-func (p *PBRSpecularGlossiness) UnmarshalJSON(data []byte) error {
-	type alias PBRSpecularGlossiness
-	tmp := alias(*NewPBRSpecularGlossiness())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*p = PBRSpecularGlossiness(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the pbr with the correct default values.
-func (p *PBRSpecularGlossiness) MarshalJSON() ([]byte, error) {
-	type alias PBRSpecularGlossiness
-	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(p)})
-	if err == nil {
-		if p.GlossinessFactor == 1 {
-			out = removeProperty([]byte(`"glossinessFactor":1`), out)
-		}
-		if p.DiffuseFactor == [4]float64{1, 1, 1, 1} {
-			out = removeProperty([]byte(`"diffuseFactor":[1,1,1,1]`), out)
-		}
-		if p.SpecularFactor == [3]float64{1, 1, 1} {
-			out = removeProperty([]byte(`"specularFactor":[1,1,1]`), out)
-		}
-		out = sanitizeJSON(out)
-	}
-	return out, err
-}
diff --git a/specular/specular.go b/specular/specular.go
new file mode 100644
index 0000000..7eb8374
--- /dev/null
+++ b/specular/specular.go
@@ -0,0 +1,71 @@
+package specular
+
+import (
+	"bytes"
+	"encoding/json"
+
+	"github.com/qmuntal/gltf"
+)
+
+const (
+	// ExtPBRSpecularGlossiness defines the PBRSpecularGlossiness unique key.
+	ExtPBRSpecularGlossiness = "KHR_materials_pbrSpecularGlossiness"
+)
+
+// New returns a new specular.PBRSpecularGlossiness.
+func New() json.Unmarshaler {
+	return new(PBRSpecularGlossiness)
+}
+
+func init() {
+	gltf.RegisterExtension(ExtPBRSpecularGlossiness, New)
+}
+
+// PBRSpecularGlossiness defines a specular-glossiness material model.
+type PBRSpecularGlossiness struct {
+	DiffuseFactor             *gltf.RGBA        `json:"diffuseFactor,omitempty"`
+	DiffuseTexture            *gltf.TextureInfo `json:"diffuseTexture,omitempty"`
+	SpecularFactor            *gltf.RGB         `json:"specularFactor,omitempty"`
+	GlossinessFactor          *float64          `json:"glossinessFactor,omitempty" validate:"omitempty,gte=0,lte=1"`
+	SpecularGlossinessTexture *gltf.TextureInfo `json:"specularGlossinessTexture,omitempty"`
+}
+
+// UnmarshalJSON unmarshal the pbr with the correct default values.
+func (p *PBRSpecularGlossiness) UnmarshalJSON(data []byte) error {
+	type alias PBRSpecularGlossiness
+	tmp := alias(PBRSpecularGlossiness{DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB(), GlossinessFactor: gltf.Float64(1)})
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*p = PBRSpecularGlossiness(tmp)
+	}
+	return err
+}
+
+// MarshalJSON marshal the pbr with the correct default values.
+func (p *PBRSpecularGlossiness) MarshalJSON() ([]byte, error) {
+	type alias PBRSpecularGlossiness
+	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(p)})
+	if err == nil {
+		if p.GlossinessFactor != nil && *p.GlossinessFactor == 1 {
+			out = removeProperty([]byte(`"glossinessFactor":1`), out)
+		}
+		if p.DiffuseFactor != nil && *p.DiffuseFactor == *gltf.NewRGBA() {
+			out = removeProperty([]byte(`"diffuseFactor":[1,1,1,1]`), out)
+		}
+		if p.SpecularFactor != nil && *p.SpecularFactor == *gltf.NewRGB() {
+			out = removeProperty([]byte(`"specularFactor":[1,1,1]`), out)
+		}
+		out = sanitizeJSON(out)
+	}
+	return out, err
+}
+
+func removeProperty(str []byte, b []byte) []byte {
+	b = bytes.Replace(b, str, []byte(""), 1)
+	return bytes.Replace(b, []byte(`,,`), []byte(","), 1)
+}
+
+func sanitizeJSON(b []byte) []byte {
+	b = bytes.Replace(b, []byte(`{,`), []byte("{"), 1)
+	return bytes.Replace(b, []byte(`,}`), []byte("}"), 1)
+}
diff --git a/struct.go b/struct.go
index 557aef3..eeb8456 100644
--- a/struct.go
+++ b/struct.go
@@ -4,10 +4,21 @@ import (
 	"bytes"
 	"encoding/base64"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"strings"
 )
 
+// Index is an utility function that returns a pointer to a uint32.
+func Index(i uint32) *uint32 {
+	return &i
+}
+
+// Float64 is an utility function that returns a pointer to a float64.
+func Float64(val float64) *float64 {
+	return &val
+}
+
 // An Asset is metadata about the glTF asset.
 type Asset struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
@@ -35,38 +46,12 @@ type Document struct {
 	Meshes             []Mesh       `json:"meshes,omitempty" validate:"dive"`
 	Nodes              []Node       `json:"nodes,omitempty" validate:"dive"`
 	Samplers           []Sampler    `json:"samplers,omitempty" validate:"dive"`
-	Scene              int32        `json:"scene" validate:"gte=-1"`
+	Scene              *uint32      `json:"scene,omitempty"`
 	Scenes             []Scene      `json:"scenes,omitempty" validate:"dive"`
 	Skins              []Skin       `json:"skins,omitempty" validate:"dive"`
 	Textures           []Texture    `json:"textures,omitempty" validate:"dive"`
 }
 
-// UnmarshalJSON unmarshal the document with the correct default values.
-func (d *Document) UnmarshalJSON(data []byte) error {
-	type alias Document
-	tmp := &alias{Scene: -1}
-	err := json.Unmarshal(data, tmp)
-	if err == nil {
-		*d = Document(*tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the document with the correct default values.
-func (d *Document) MarshalJSON() ([]byte, error) {
-	type alias Document
-	if d.Scene == -1 {
-		return json.Marshal(&struct {
-			Scene int32 `json:"scene,omitempty"`
-			*alias
-		}{
-			Scene: 0,
-			alias: (*alias)(d),
-		})
-	}
-	return json.Marshal(&struct{ *alias }{alias: (*alias)(d)})
-}
-
 // An Accessor is a typed view into a bufferView.
 // An accessor provides a typed view into a bufferView or a subset of a bufferView
 // similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer.
@@ -74,48 +59,17 @@ type Accessor struct {
 	Extensions    Extensions    `json:"extensions,omitempty"`
 	Extras        interface{}   `json:"extras,omitempty"`
 	Name          string        `json:"name,omitempty"`
-	BufferView    int32         `json:"bufferView" validate:"gte=-1"`
+	BufferView    *uint32       `json:"bufferView,omitempty"`
 	ByteOffset    uint32        `json:"byteOffset,omitempty"`
-	ComponentType ComponentType `json:"componentType" validate:"oneof=5120 5121 5122 5123 5125 5126"`
+	ComponentType ComponentType `json:"componentType" validate:"lte=5"`
 	Normalized    bool          `json:"normalized,omitempty"`      // Specifies whether integer data values should be normalized.
 	Count         uint32        `json:"count" validate:"required"` // The number of attributes referenced by this accessor.
-	Type          AccessorType  `json:"type" validate:"oneof=SCALAR VEC2 VEC3 VEC4 MAT2 MAT3 MAT4"`
+	Type          AccessorType  `json:"type" validate:"lte=6"`
 	Max           []float64     `json:"max,omitempty" validate:"omitempty,lte=16"` // Maximum value of each component in this attribute.
 	Min           []float64     `json:"min,omitempty" validate:"omitempty,lte=16"` // Minimum value of each component in this attribute.
 	Sparse        *Sparse       `json:"sparse,omitempty"`                          // Sparse storage of attributes that deviate from their initialization value.
 }
 
-// NewAccessor returns a default accessor.
-func NewAccessor() *Accessor {
-	return &Accessor{BufferView: -1}
-}
-
-// UnmarshalJSON unmarshal the accessor with the correct default values.
-func (a *Accessor) UnmarshalJSON(data []byte) error {
-	type alias Accessor
-	tmp := alias(*NewAccessor())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*a = Accessor(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the accessor with the correct default values.
-func (a *Accessor) MarshalJSON() ([]byte, error) {
-	type alias Accessor
-	if a.BufferView == -1 {
-		return json.Marshal(&struct {
-			BufferView int32 `json:"bufferView,omitempty"`
-			*alias
-		}{
-			BufferView: 0,
-			alias:      (*alias)(a),
-		})
-	}
-	return json.Marshal(&struct{ *alias }{alias: (*alias)(a)})
-}
-
 // Sparse storage of attributes that deviate from their initialization value.
 type Sparse struct {
 	Extensions Extensions    `json:"extensions,omitempty"`
@@ -139,7 +93,7 @@ type SparseIndices struct {
 	Extras        interface{}   `json:"extras,omitempty"`
 	BufferView    uint32        `json:"bufferView"`
 	ByteOffset    uint32        `json:"byteOffset,omitempty"`
-	ComponentType ComponentType `json:"componentType" validate:"oneof=5121 5123 5125"`
+	ComponentType ComponentType `json:"componentType" validate:"oneof=2 4 5"`
 }
 
 // A Buffer points to binary geometry, animation, or skins.
@@ -175,39 +129,13 @@ func (b *Buffer) marshalData() ([]uint8, error) {
 type BufferView struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
-	Buffer     int32       `json:"buffer" validate:"gte=-1"`
+	Buffer     uint32      `json:"buffer"`
 	ByteOffset uint32      `json:"byteOffset,omitempty"`
 	ByteLength uint32      `json:"byteLength" validate:"required"`
 	ByteStride uint32      `json:"byteStride,omitempty" validate:"omitempty,gte=4,lte=252"`
 	Target     Target      `json:"target,omitempty" validate:"omitempty,oneof=34962 34963"`
 }
 
-// UnmarshalJSON unmarshal the buffer view with the correct default values.
-func (b *BufferView) UnmarshalJSON(data []byte) error {
-	type alias BufferView
-	tmp := &alias{Buffer: -1}
-	err := json.Unmarshal(data, tmp)
-	if err == nil {
-		*b = BufferView(*tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the buffer view with the correct default values.
-func (b *BufferView) MarshalJSON() ([]byte, error) {
-	type alias BufferView
-	if b.Buffer == -1 {
-		return json.Marshal(&struct {
-			Buffer int32 `json:"buffer,omitempty"`
-			*alias
-		}{
-			Buffer: 0,
-			alias:  (*alias)(b),
-		})
-	}
-	return json.Marshal(&struct{ *alias }{alias: (*alias)(b)})
-}
-
 // The Scene contains a list of root nodes.
 type Scene struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
@@ -222,33 +150,54 @@ type Node struct {
 	Extensions  Extensions  `json:"extensions,omitempty"`
 	Extras      interface{} `json:"extras,omitempty"`
 	Name        string      `json:"name,omitempty"`
-	Camera      int32       `json:"camera" validate:"gte=-1"`
+	Camera      *uint32     `json:"camera,omitempty"`
 	Children    []uint32    `json:"children,omitempty" validate:"omitempty,unique"`
-	Skin        int32       `json:"skin" validate:"gte=-1"`
+	Skin        *uint32     `json:"skin,omitempty"`
 	Matrix      [16]float64 `json:"matrix"` // A 4x4 transformation matrix stored in column-major order.
-	Mesh        int32       `json:"mesh" validate:"gte=-1"`
+	Mesh        *uint32     `json:"mesh,omitempty"`
 	Rotation    [4]float64  `json:"rotation" validate:"omitempty,dive,gte=-1,lte=1"` // The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar.
 	Scale       [3]float64  `json:"scale"`
 	Translation [3]float64  `json:"translation"`
 	Weights     []float64   `json:"weights,omitempty"` // The weights of the instantiated Morph Target.
 }
 
-// NewNode returns a default Node.
-func NewNode() *Node {
-	return &Node{
-		Matrix:   [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
-		Rotation: [4]float64{0, 0, 0, 1},
-		Scale:    [3]float64{1, 1, 1},
-		Camera:   -1,
-		Skin:     -1,
-		Mesh:     -1,
+// MatrixOrDefault returns the node matrix if it represents a valid affine matrix, else return the default one.
+func (n *Node) MatrixOrDefault() [16]float64 {
+	if n.Matrix == emptyMatrix {
+		return DefaultMatrix
 	}
+	return n.Matrix
+}
+
+// RotationOrDefault returns the node rotation if it represents a valid quaternion, else return the default one.
+func (n *Node) RotationOrDefault() [4]float64 {
+	if n.Rotation == emptyRotation {
+		return DefaultRotation
+	}
+	return n.Rotation
+}
+
+// ScaleOrDefault returns the node scale if it represents a valid scale factor, else return the default one.
+func (n *Node) ScaleOrDefault() [3]float64 {
+	if n.Scale == emptyScale {
+		return DefaultScale
+	}
+	return n.Scale
+}
+
+// TranslationOrDefault returns the node translation.
+func (n *Node) TranslationOrDefault() [3]float64 {
+	return n.Translation
 }
 
 // UnmarshalJSON unmarshal the node with the correct default values.
 func (n *Node) UnmarshalJSON(data []byte) error {
 	type alias Node
-	tmp := alias(*NewNode())
+	tmp := alias(Node{
+		Matrix:   DefaultMatrix,
+		Rotation: DefaultRotation,
+		Scale:    DefaultScale,
+	})
 	err := json.Unmarshal(data, &tmp)
 	if err == nil {
 		*n = Node(tmp)
@@ -261,27 +210,27 @@ func (n *Node) MarshalJSON() ([]byte, error) {
 	type alias Node
 	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(n)})
 	if err == nil {
-		if n.Matrix == [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1} {
+		if n.Matrix == DefaultMatrix {
 			out = removeProperty([]byte(`"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]`), out)
+		} else if n.Matrix == emptyMatrix {
+			out = removeProperty([]byte(`"matrix":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]`), out)
 		}
-		if n.Rotation == [4]float64{0, 0, 0, 1} {
+
+		if n.Rotation == DefaultRotation {
 			out = removeProperty([]byte(`"rotation":[0,0,0,1]`), out)
+		} else if n.Rotation == emptyRotation {
+			out = removeProperty([]byte(`"rotation":[0,0,0,0]`), out)
 		}
-		if n.Scale == [3]float64{1, 1, 1} {
+
+		if n.Scale == DefaultScale {
 			out = removeProperty([]byte(`"scale":[1,1,1]`), out)
+		} else if n.Scale == emptyScale {
+			out = removeProperty([]byte(`"scale":[0,0,0]`), out)
 		}
-		if n.Translation == [3]float64{0, 0, 0} {
+
+		if n.Translation == DefaultTranslation {
 			out = removeProperty([]byte(`"translation":[0,0,0]`), out)
 		}
-		if n.Camera == -1 {
-			out = removeProperty([]byte(`"camera":-1`), out)
-		}
-		if n.Skin == -1 {
-			out = removeProperty([]byte(`"skin":-1`), out)
-		}
-		if n.Mesh == -1 {
-			out = removeProperty([]byte(`"mesh":-1`), out)
-		}
 		out = sanitizeJSON(out)
 	}
 	return out, err
@@ -292,41 +241,9 @@ type Skin struct {
 	Extensions          Extensions  `json:"extensions,omitempty"`
 	Extras              interface{} `json:"extras,omitempty"`
 	Name                string      `json:"name,omitempty"`
-	InverseBindMatrices int32       `json:"inverseBindMatrices" validate:"gte=-1"` // The index of the accessor containing the floating-point 4x4 inverse-bind matrices.
-	Skeleton            int32       `json:"skeleton" validate:"gte=-1"`            // The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root.
-	Joints              []uint32    `json:"joints" validate:"omitempty,unique"`    // Indices of skeleton nodes, used as joints in this skin.
-}
-
-// NewSkin create a default Skin.
-func NewSkin() *Skin {
-	return &Skin{InverseBindMatrices: -1, Skeleton: -1}
-}
-
-// UnmarshalJSON unmarshal the skin with the correct default values.
-func (s *Skin) UnmarshalJSON(data []byte) error {
-	type alias Skin
-	tmp := alias(*NewSkin())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*s = Skin(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the skin with the correct default values.
-func (s *Skin) MarshalJSON() ([]byte, error) {
-	type alias Skin
-	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(s)})
-	if err == nil {
-		if s.InverseBindMatrices == -1 {
-			out = removeProperty([]byte(`"inverseBindMatrices":-1`), out)
-		}
-		if s.Skeleton == -1 {
-			out = removeProperty([]byte(`"skeleton":-1`), out)
-		}
-		out = sanitizeJSON(out)
-	}
-	return out, err
+	InverseBindMatrices *uint32     `json:"inverseBindMatrices,omitempty"`      // The index of the accessor containing the floating-point 4x4 inverse-bind matrices.
+	Skeleton            *uint32     `json:"skeleton,omitempty"`                 // The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root.
+	Joints              []uint32    `json:"joints" validate:"omitempty,unique"` // Indices of skeleton nodes, used as joints in this skin.
 }
 
 // A Camera projection. A node can reference a camera to apply a transform to place the camera in the scene.
@@ -336,7 +253,29 @@ type Camera struct {
 	Name         string        `json:"name,omitempty"`
 	Orthographic *Orthographic `json:"orthographic,omitempty"`
 	Perspective  *Perspective  `json:"perspective,omitempty"`
-	Type         CameraType    `json:"type" validate:"oneof=perspective orthographic"`
+}
+
+// MarshalJSON marshal the camera with the correct default values.
+func (c *Camera) MarshalJSON() ([]byte, error) {
+	type alias Camera
+	if c.Perspective != nil {
+		return json.Marshal(&struct {
+			Type string `json:"type"`
+			*alias
+		}{
+			Type:  "perspective",
+			alias: (*alias)(c),
+		})
+	} else if c.Orthographic != nil {
+		return json.Marshal(&struct {
+			Type string `json:"type"`
+			*alias
+		}{
+			Type:  "orthographic",
+			alias: (*alias)(c),
+		})
+	}
+	return nil, errors.New("gltf: camera must defined either the perspective or orthographic property")
 }
 
 // Orthographic camera containing properties to create an orthographic projection matrix.
@@ -373,44 +312,12 @@ type Primitive struct {
 	Extensions Extensions    `json:"extensions,omitempty"`
 	Extras     interface{}   `json:"extras,omitempty"`
 	Attributes Attribute     `json:"attributes"`
-	Indices    int32         `json:"indices" validate:"gte=-1"` // The index of the accessor that contains the indices.
-	Material   int32         `json:"material" validate:"gte=-1"`
-	Mode       PrimitiveMode `json:"mode" validate:"lte=6"`
+	Indices    *uint32       `json:"indices,omitempty"` // The index of the accessor that contains the indices.
+	Material   *uint32       `json:"material,omitempty"`
+	Mode       PrimitiveMode `json:"mode,omitempty" validate:"lte=6"`
 	Targets    []Attribute   `json:"targets,omitempty" validate:"omitempty,dive,dive,keys,oneof=POSITION NORMAL TANGENT,endkeys"` // Only POSITION, NORMAL, and TANGENT supported.
 }
 
-// NewPrimitive create a default Primitive.
-func NewPrimitive() *Primitive {
-	return &Primitive{Mode: Triangles, Indices: -1, Material: -1}
-}
-
-// UnmarshalJSON unmarshal the primitive with the correct default values.
-func (p *Primitive) UnmarshalJSON(data []byte) error {
-	type alias Primitive
-	tmp := alias(*NewPrimitive())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*p = Primitive(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the primitive with the correct default values.
-func (p *Primitive) MarshalJSON() ([]byte, error) {
-	type alias Primitive
-	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(p)})
-	if err == nil {
-		if p.Indices == -1 {
-			out = removeProperty([]byte(`"indices":-1`), out)
-		}
-		if p.Material == -1 {
-			out = removeProperty([]byte(`"material":-1`), out)
-		}
-		out = sanitizeJSON(out)
-	}
-	return out, err
-}
-
 // The Material appearance of a primitive.
 type Material struct {
 	Extensions           Extensions            `json:"extensions,omitempty"`
@@ -421,20 +328,23 @@ type Material struct {
 	OcclusionTexture     *OcclusionTexture     `json:"occlusionTexture,omitempty"`
 	EmissiveTexture      *TextureInfo          `json:"emissiveTexture,omitempty"`
 	EmissiveFactor       [3]float64            `json:"emissiveFactor,omitempty" validate:"dive,gte=0,lte=1"`
-	AlphaMode            AlphaMode             `json:"alphaMode,omitempty" validate:"oneof=OPAQUE MASK BLEND"`
-	AlphaCutoff          float64               `json:"alphaCutoff" validate:"gte=0"`
+	AlphaMode            AlphaMode             `json:"alphaMode,omitempty" validate:"lte=2"`
+	AlphaCutoff          *float64              `json:"alphaCutoff,omitempty" validate:"omitempty,gte=0"`
 	DoubleSided          bool                  `json:"doubleSided,omitempty"`
 }
 
-// NewMaterial create a default Material.
-func NewMaterial() *Material {
-	return &Material{AlphaCutoff: 0.5, AlphaMode: Opaque}
+// AlphaCutoffOrDefault returns the scale if it is not nil, else return the default one.
+func (m *Material) AlphaCutoffOrDefault() float64 {
+	if m.AlphaCutoff == nil {
+		return 0.5
+	}
+	return *m.AlphaCutoff
 }
 
 // UnmarshalJSON unmarshal the material with the correct default values.
 func (m *Material) UnmarshalJSON(data []byte) error {
 	type alias Material
-	tmp := alias(*NewMaterial())
+	tmp := alias(Material{AlphaCutoff: Float64(0.5)})
 	err := json.Unmarshal(data, &tmp)
 	if err == nil {
 		*m = Material(tmp)
@@ -447,12 +357,9 @@ func (m *Material) MarshalJSON() ([]byte, error) {
 	type alias Material
 	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(m)})
 	if err == nil {
-		if m.AlphaCutoff == 0.5 {
+		if m.AlphaCutoff != nil && *m.AlphaCutoff == 0.5 {
 			out = removeProperty([]byte(`"alphaCutoff":0.5`), out)
 		}
-		if m.AlphaMode == Opaque {
-			out = removeProperty([]byte(`"alphaMode":"OPAQUE"`), out)
-		}
 		if m.EmissiveFactor == [3]float64{0, 0, 0} {
 			out = removeProperty([]byte(`"emissiveFactor":[0,0,0]`), out)
 		}
@@ -465,20 +372,23 @@ func (m *Material) MarshalJSON() ([]byte, error) {
 type NormalTexture struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
-	Index      int32       `json:"index" validate:"gte=-1"`
+	Index      *uint32     `json:"index,omitempty"`
 	TexCoord   uint32      `json:"texCoord,omitempty"` // The index of texture's TEXCOORD attribute used for texture coordinate mapping.
-	Scale      float64     `json:"scale"`
+	Scale      *float64    `json:"scale,omitempty"`
 }
 
-// NewNormalTexture returns a default NormalTexture.
-func NewNormalTexture(index int32) *NormalTexture {
-	return &NormalTexture{Index: index, Scale: 1}
+// ScaleOrDefault returns the scale if it is not nil, else return the default one.
+func (n *NormalTexture) ScaleOrDefault() float64 {
+	if n.Scale == nil {
+		return 1
+	}
+	return *n.Scale
 }
 
 // UnmarshalJSON unmarshal the texture info with the correct default values.
 func (n *NormalTexture) UnmarshalJSON(data []byte) error {
 	type alias NormalTexture
-	tmp := alias(*NewNormalTexture(-1))
+	tmp := alias(NormalTexture{Scale: Float64(1)})
 	err := json.Unmarshal(data, &tmp)
 	if err == nil {
 		*n = NormalTexture(tmp)
@@ -489,37 +399,39 @@ func (n *NormalTexture) UnmarshalJSON(data []byte) error {
 // MarshalJSON marshal the texture info with the correct default values.
 func (n *NormalTexture) MarshalJSON() ([]byte, error) {
 	type alias NormalTexture
-	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(n)})
-	if err == nil {
-		if n.Index == -1 {
-			out = removeProperty([]byte(`"index":-1`), out)
-		}
-		if n.Scale == -1 {
-			out = removeProperty([]byte(`"scale":-1`), out)
-		}
-		out = sanitizeJSON(out)
+	if n.Scale != nil && *n.Scale == 1 {
+		return json.Marshal(&struct {
+			Scale float64 `json:"scale,omitempty"`
+			*alias
+		}{
+			Scale: 0,
+			alias: (*alias)(n),
+		})
 	}
-	return out, err
+	return json.Marshal(&struct{ *alias }{alias: (*alias)(n)})
 }
 
 // An OcclusionTexture references to an occlusion texture
 type OcclusionTexture struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
-	Index      int32       `json:"index" validate:"gte=-1"`
+	Index      *uint32     `json:"index,omitempty"`
 	TexCoord   uint32      `json:"texCoord,omitempty"` // The index of texture's TEXCOORD attribute used for texture coordinate mapping.
-	Strength   float64     `json:"strength" validate:"gte=0,lte=1"`
+	Strength   *float64    `json:"strength,omitempty" validate:"omitempty,gte=0,lte=1"`
 }
 
-// NewOcclusionTexture returns a default OcclusionTexture.
-func NewOcclusionTexture(index int32) *OcclusionTexture {
-	return &OcclusionTexture{Index: index, Strength: 1}
+// StrengthOrDefault returns the strength if it is not nil, else return the default one.
+func (o *OcclusionTexture) StrengthOrDefault() float64 {
+	if o.Strength == nil {
+		return 1
+	}
+	return *o.Strength
 }
 
 // UnmarshalJSON unmarshal the texture info with the correct default values.
 func (o *OcclusionTexture) UnmarshalJSON(data []byte) error {
 	type alias OcclusionTexture
-	tmp := alias(*NewOcclusionTexture(-1))
+	tmp := alias(OcclusionTexture{Strength: Float64(1)})
 	err := json.Unmarshal(data, &tmp)
 	if err == nil {
 		*o = OcclusionTexture(tmp)
@@ -530,39 +442,109 @@ func (o *OcclusionTexture) UnmarshalJSON(data []byte) error {
 // MarshalJSON marshal the texture info with the correct default values.
 func (o *OcclusionTexture) MarshalJSON() ([]byte, error) {
 	type alias OcclusionTexture
-	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(o)})
+	if o.Strength != nil && *o.Strength == 1 {
+		return json.Marshal(&struct {
+			Strength float64 `json:"strength,omitempty"`
+			*alias
+		}{
+			Strength: 0,
+			alias:    (*alias)(o),
+		})
+	}
+	return json.Marshal(&struct{ *alias }{alias: (*alias)(o)})
+}
+
+// The RGBA components of a color.
+// Each element must be greater than or equal to 0 and less than or equal to 1.
+type RGBA struct {
+	R, G, B, A float64 `validate:"gte=0,lte=1"`
+}
+
+// NewRGBA returns a default RGBA color.
+func NewRGBA() *RGBA {
+	return &RGBA{1, 1, 1, 1}
+}
+
+// UnmarshalJSON unmarshal the color with the correct default values.
+func (c *RGBA) UnmarshalJSON(data []byte) error {
+	tmp := [4]float64{1, 1, 1, 1}
+	err := json.Unmarshal(data, &tmp)
 	if err == nil {
-		if o.Index == -1 {
-			out = removeProperty([]byte(`"index":-1`), out)
-		}
-		if o.Strength == 1 {
-			out = removeProperty([]byte(`"strength":1`), out)
-		}
-		out = sanitizeJSON(out)
+		c.R, c.G, c.B, c.A = tmp[0], tmp[1], tmp[2], tmp[3]
 	}
-	return out, err
+	return err
+}
+
+// MarshalJSON marshal the color with the correct default values.
+func (c *RGBA) MarshalJSON() ([]byte, error) {
+	return json.Marshal([4]float64{c.R, c.G, c.B, c.A})
+}
+
+// The RGB components of a color.
+// Each element must be greater than or equal to 0 and less than or equal to 1.
+type RGB struct {
+	R, G, B float64 `validate:"gte=0,lte=1"`
+}
+
+// NewRGB returns a default RGB color.
+func NewRGB() *RGB {
+	return &RGB{1, 1, 1}
+}
+
+// UnmarshalJSON unmarshal the color with the correct default values.
+func (c *RGB) UnmarshalJSON(data []byte) error {
+	tmp := [3]float64{1, 1, 1}
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		c.R, c.G, c.B = tmp[0], tmp[1], tmp[2]
+	}
+	return err
+}
+
+// MarshalJSON marshal the color with the correct default values.
+func (c *RGB) MarshalJSON() ([]byte, error) {
+	return json.Marshal([3]float64{c.R, c.G, c.B})
 }
 
 // PBRMetallicRoughness defines a set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology.
 type PBRMetallicRoughness struct {
 	Extensions               Extensions   `json:"extensions,omitempty"`
 	Extras                   interface{}  `json:"extras,omitempty"`
-	BaseColorFactor          [4]float64   `json:"baseColorFactor" validate:"dive,gte=0,lte=1"`
+	BaseColorFactor          *RGBA        `json:"baseColorFactor,omitempty"`
 	BaseColorTexture         *TextureInfo `json:"baseColorTexture,omitempty"`
-	MetallicFactor           float64      `json:"metallicFactor" validate:"gte=0,lte=1"`
-	RoughnessFactor          float64      `json:"roughnessFactor" validate:"gte=0,lte=1"`
+	MetallicFactor           *float64     `json:"metallicFactor,omitempty" validate:"omitempty,gte=0,lte=1"`
+	RoughnessFactor          *float64     `json:"roughnessFactor,omitempty" validate:"omitempty,gte=0,lte=1"`
 	MetallicRoughnessTexture *TextureInfo `json:"metallicRoughnessTexture,omitempty"`
 }
 
-// NewPBRMetallicRoughness returns a default PBRMetallicRoughness.
-func NewPBRMetallicRoughness() *PBRMetallicRoughness {
-	return &PBRMetallicRoughness{BaseColorFactor: [4]float64{1, 1, 1, 1}, MetallicFactor: 1, RoughnessFactor: 1}
+// MetallicFactorOrDefault returns the metallic factor if it is not nil, else return the default one.
+func (p *PBRMetallicRoughness) MetallicFactorOrDefault() float64 {
+	if p.MetallicFactor == nil {
+		return 1
+	}
+	return *p.MetallicFactor
+}
+
+// RoughnessFactorOrDefault returns the roughness factor if it is not nil, else return the default one.
+func (p *PBRMetallicRoughness) RoughnessFactorOrDefault() float64 {
+	if p.RoughnessFactor == nil {
+		return 1
+	}
+	return *p.RoughnessFactor
+}
+
+// BaseColorFactorOrDefault returns the base color factor if it is not nil, else return the default one.
+func (p *PBRMetallicRoughness) BaseColorFactorOrDefault() RGBA {
+	if p.BaseColorFactor == nil {
+		return *NewRGBA()
+	}
+	return *p.BaseColorFactor
 }
 
 // UnmarshalJSON unmarshal the pbr with the correct default values.
 func (p *PBRMetallicRoughness) UnmarshalJSON(data []byte) error {
 	type alias PBRMetallicRoughness
-	tmp := alias(*NewPBRMetallicRoughness())
+	tmp := alias(PBRMetallicRoughness{BaseColorFactor: NewRGBA(), MetallicFactor: Float64(1), RoughnessFactor: Float64(1)})
 	err := json.Unmarshal(data, &tmp)
 	if err == nil {
 		*p = PBRMetallicRoughness(tmp)
@@ -575,13 +557,13 @@ func (p *PBRMetallicRoughness) MarshalJSON() ([]byte, error) {
 	type alias PBRMetallicRoughness
 	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(p)})
 	if err == nil {
-		if p.MetallicFactor == 1 {
+		if p.MetallicFactor != nil && *p.MetallicFactor == 1 {
 			out = removeProperty([]byte(`"metallicFactor":1`), out)
 		}
-		if p.RoughnessFactor == 1 {
+		if p.RoughnessFactor != nil && *p.RoughnessFactor == 1 {
 			out = removeProperty([]byte(`"roughnessFactor":1`), out)
 		}
-		if p.BaseColorFactor == [4]float64{1, 1, 1, 1} {
+		if p.BaseColorFactor != nil && *p.BaseColorFactor == *NewRGBA() {
 			out = removeProperty([]byte(`"baseColorFactor":[1,1,1,1]`), out)
 		}
 		out = sanitizeJSON(out)
@@ -593,80 +575,17 @@ func (p *PBRMetallicRoughness) MarshalJSON() ([]byte, error) {
 type TextureInfo struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
-	Index      int32       `json:"index" validate:"gte=-1"`
+	Index      *uint32     `json:"index,omitempty"`
 	TexCoord   uint32      `json:"texCoord,omitempty"` // The index of texture's TEXCOORD attribute used for texture coordinate mapping.
 }
 
-// NewTextureInfo returns a default TextureInfo.
-func NewTextureInfo(index int32) *TextureInfo {
-	return &TextureInfo{Index: index}
-}
-
-// UnmarshalJSON unmarshal the texture info with the correct default values.
-func (t *TextureInfo) UnmarshalJSON(data []byte) error {
-	type alias TextureInfo
-	tmp := alias(*NewTextureInfo(-1))
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*t = TextureInfo(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the texture info with the correct default values.
-func (t *TextureInfo) MarshalJSON() ([]byte, error) {
-	type alias TextureInfo
-	if t.Index == -1 {
-		return json.Marshal(&struct {
-			Index int32 `json:"index,omitempty"`
-			*alias
-		}{
-			Index: 0,
-			alias: (*alias)(t),
-		})
-	}
-	return json.Marshal(&struct{ *alias }{alias: (*alias)(t)})
-}
-
 // A Texture and its sampler.
 type Texture struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
 	Name       string      `json:"name,omitempty"`
-	Sampler    int32       `json:"sampler" validate:"gte=-1"`
-	Source     int32       `json:"source" validate:"gte=-1"`
-}
-
-// NewTexture returns a default Texture.
-func NewTexture() *Texture {
-	return &Texture{Sampler: -1, Source: -1}
-}
-
-// UnmarshalJSON unmarshal the texture with the correct default values.
-func (t *Texture) UnmarshalJSON(data []byte) error {
-	type alias Texture
-	tmp := alias(*NewTexture())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*t = Texture(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the texture with the correct default values.
-func (t *Texture) MarshalJSON() ([]byte, error) {
-	type alias Texture
-	out, err := json.Marshal(&struct{ *alias }{alias: (*alias)(t)})
-	if err == nil {
-		if t.Sampler == -1 {
-			out = removeProperty([]byte(`"sampler":-1`), out)
-		}
-		if t.Source == -1 {
-			out = removeProperty([]byte(`"source":-1`), out)
-		}
-		out = sanitizeJSON(out)
-	}
-	return out, err
+	Sampler    *uint32     `json:"sampler,omitempty"`
+	Source     *uint32     `json:"source,omitempty"`
 }
 
 // Sampler of a texture for filtering and wrapping modes.
@@ -674,26 +593,10 @@ type Sampler struct {
 	Extensions Extensions   `json:"extensions,omitempty"`
 	Extras     interface{}  `json:"extras,omitempty"`
 	Name       string       `json:"name,omitempty"`
-	MagFilter  MagFilter    `json:"magFilter,omitempty" validate:"omitempty,oneof=9728 9729"`
-	MinFilter  MinFilter    `json:"minFilter,omitempty" validate:"omitempty,oneof=9728 9729 9984 9985 9986 9987"`
-	WrapS      WrappingMode `json:"wrapS,omitempty" validate:"omitempty,oneof=33071 33648 10497"`
-	WrapT      WrappingMode `json:"wrapT,omitempty" validate:"omitempty,oneof=33071 33648 10497"`
-}
-
-// NewSampler returns a default Sampler.
-func NewSampler() *Sampler {
-	return &Sampler{WrapS: Repeat, WrapT: Repeat}
-}
-
-// UnmarshalJSON unmarshal the sampler with the correct default values.
-func (s *Sampler) UnmarshalJSON(data []byte) error {
-	type alias Sampler
-	tmp := alias(*NewSampler())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*s = Sampler(tmp)
-	}
-	return err
+	MagFilter  MagFilter    `json:"magFilter,omitempty" validate:"lte=1"`
+	MinFilter  MinFilter    `json:"minFilter,omitempty" validate:"lte=5"`
+	WrapS      WrappingMode `json:"wrapS,omitempty" validate:"lte=2"`
+	WrapT      WrappingMode `json:"wrapT,omitempty" validate:"lte=2"`
 }
 
 // Image data used to create a texture. Image can be referenced by URI or bufferView index.
@@ -738,66 +641,19 @@ type Animation struct {
 type AnimationSampler struct {
 	Extensions    Extensions    `json:"extensions,omitempty"`
 	Extras        interface{}   `json:"extras,omitempty"`
-	Input         int32         `json:"input" validate:"gte=-1"` // The index of an accessor containing keyframe input values.
-	Interpolation Interpolation `json:"interpolation,omitempty" validate:"omitempty,oneof=LINEAR STEP CUBICSPLINE"`
-	Output        int32         `json:"output" validate:"gte=-1"` // The index of an accessor containing keyframe output values.
-}
-
-// NewAnimationSampler returns a default AnimationSampler.
-func NewAnimationSampler() *AnimationSampler {
-	return &AnimationSampler{Input: -1, Interpolation: Linear, Output: -1}
-}
-
-// UnmarshalJSON unmarshal the animation sampler with the correct default values.
-func (as *AnimationSampler) UnmarshalJSON(data []byte) error {
-	type alias AnimationSampler
-	tmp := alias(*NewAnimationSampler())
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*as = AnimationSampler(tmp)
-	}
-	return err
+	Input         *uint32       `json:"input,omitempty"` // The index of an accessor containing keyframe input values.
+	Interpolation Interpolation `json:"interpolation,omitempty" validate:"lte=2"`
+	Output        *uint32       `json:"output,omitempty"` // The index of an accessor containing keyframe output values.
 }
 
 // The Channel targets an animation's sampler at a node's property.
 type Channel struct {
 	Extensions Extensions    `json:"extensions,omitempty"`
 	Extras     interface{}   `json:"extras,omitempty"`
-	Sampler    int32         `json:"sampler" validate:"gte=-1"`
+	Sampler    *uint32       `json:"sampler,omitempty"`
 	Target     ChannelTarget `json:"target"`
 }
 
-// NewChannel returns a default Channel.
-func NewChannel(sampler int32) *Channel {
-	return &Channel{Sampler: sampler}
-}
-
-// UnmarshalJSON unmarshal the channel with the correct default values.
-func (ch *Channel) UnmarshalJSON(data []byte) error {
-	type alias Channel
-	tmp := alias(*NewChannel(-1))
-	err := json.Unmarshal(data, &tmp)
-	if err == nil {
-		*ch = Channel(tmp)
-	}
-	return err
-}
-
-// MarshalJSON marshal the channel with the correct default values.
-func (ch *Channel) MarshalJSON() ([]byte, error) {
-	type alias Channel
-	if ch.Sampler == -1 {
-		return json.Marshal(&struct {
-			Sampler int32 `json:"sampler,omitempty"`
-			*alias
-		}{
-			Sampler: 0,
-			alias:   (*alias)(ch),
-		})
-	}
-	return json.Marshal(&struct{ *alias }{alias: (*alias)(ch)})
-}
-
 // ChannelTarget describes the index of the node and TRS property that an animation channel targets.
 // The Path represents the name of the node's TRS property to modify, or the "weights" of the Morph Targets it instantiates.
 // For the "translation" property, the values that are provided by the sampler are the translation along the x, y, and z axes.
@@ -806,39 +662,50 @@ func (ch *Channel) MarshalJSON() ([]byte, error) {
 type ChannelTarget struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
-	Node       int32       `json:"node" validate:"gte=-1"`
-	Path       TRSProperty `json:"path" validate:"oneof=translation rotation scale weights"`
+	Node       *uint32     `json:"node,omitempty"`
+	Path       TRSProperty `json:"path" validate:"lte=4"`
 }
 
-// NewChannelTarget returns a default ChannelTarget.
-func NewChannelTarget(path TRSProperty) *ChannelTarget {
-	return &ChannelTarget{Node: -1, Path: path}
+// Extensions is map where the keys are the extension identifiers and the values are the extensions payloads.
+// If a key matches with one of the supported extensions the value will be marshalled as a pointer to the extension struct.
+// If a key does not match with any of the supported extensions the value will be a json.RawMessage so its decoding can be delayed.
+type Extensions map[string]interface{}
+
+type envelope map[string]json.RawMessage
+
+var extensions = make(map[string]func() json.Unmarshaler)
+
+// RegisterExtension registers a function that returns a new extension of the given
+// byte array. This is intended to be called from the init function in
+// packages that implement extensions.
+func RegisterExtension(key string, f func() json.Unmarshaler) {
+	extensions[key] = f
 }
 
-// UnmarshalJSON unmarshal the channel target with the correct default values.
-func (ch *ChannelTarget) UnmarshalJSON(data []byte) error {
-	type alias ChannelTarget
-	tmp := alias(*NewChannelTarget(""))
-	err := json.Unmarshal(data, &tmp)
+// UnmarshalJSON unmarshal the extensions with the supported extensions initialized.
+func (ext *Extensions) UnmarshalJSON(data []byte) error {
+	if len(*ext) == 0 {
+		*ext = make(Extensions)
+	}
+	var raw envelope
+	err := json.Unmarshal(data, &raw)
 	if err == nil {
-		*ch = ChannelTarget(tmp)
+		for key, value := range raw {
+			if extFactory, ok := extensions[key]; ok {
+				n := extFactory()
+				err := json.Unmarshal(value, n)
+				if err != nil {
+					(*ext)[key] = value
+				} else {
+					(*ext)[key] = n
+				}
+			} else {
+				(*ext)[key] = value
+			}
+		}
 	}
-	return err
-}
 
-// MarshalJSON marshal the channel target with the correct default values.
-func (ch *ChannelTarget) MarshalJSON() ([]byte, error) {
-	type alias ChannelTarget
-	if ch.Node == -1 {
-		return json.Marshal(&struct {
-			Node int32 `json:"node,omitempty"`
-			*alias
-		}{
-			Node:  0,
-			alias: (*alias)(ch),
-		})
-	}
-	return json.Marshal(&struct{ *alias }{alias: (*alias)(ch)})
+	return err
 }
 
 func removeProperty(str []byte, b []byte) []byte {


Test patch:
diff --git a/decoder_test.go b/decoder_test.go
index c294943..d0d9758 100644
--- a/decoder_test.go
+++ b/decoder_test.go
@@ -29,11 +29,11 @@ func TestOpen(t *testing.T) {
 		{args{"openError", ""}, nil, true},
 		{args{"testdata/Cube/glTF/Cube.gltf", ""}, &Document{
 			Accessors: []Accessor{
-				{BufferView: 0, ByteOffset: 0, ComponentType: UnsignedShort, Count: 36, Max: []float64{35}, Min: []float64{0}, Type: Scalar},
-				{BufferView: 1, ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 1, 1}, Min: []float64{-1, -1, -1}, Type: Vec3},
-				{BufferView: 2, ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 1, 1}, Min: []float64{-1, -1, -1}, Type: Vec3},
-				{BufferView: 3, ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 0, 0, 1}, Min: []float64{0, 0, -1, -1}, Type: Vec4},
-				{BufferView: 4, ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 1}, Min: []float64{-1, -1}, Type: Vec2}},
+				{BufferView: Index(0), ByteOffset: 0, ComponentType: UnsignedShort, Count: 36, Max: []float64{35}, Min: []float64{0}, Type: Scalar},
+				{BufferView: Index(1), ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 1, 1}, Min: []float64{-1, -1, -1}, Type: Vec3},
+				{BufferView: Index(2), ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 1, 1}, Min: []float64{-1, -1, -1}, Type: Vec3},
+				{BufferView: Index(3), ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 0, 0, 1}, Min: []float64{0, 0, -1, -1}, Type: Vec4},
+				{BufferView: Index(4), ByteOffset: 0, ComponentType: Float, Count: 36, Max: []float64{1, 1}, Min: []float64{-1, -1}, Type: Vec2}},
 			Asset: Asset{Generator: "VKTS glTF 2.0 exporter", Version: "2.0"},
 			BufferViews: []BufferView{
 				{Buffer: 0, ByteLength: 72, ByteOffset: 0, Target: ElementArrayBuffer},
@@ -44,20 +44,20 @@ func TestOpen(t *testing.T) {
 			},
 			Buffers:   []Buffer{{ByteLength: 1800, URI: "Cube.bin", Data: readFile("testdata/Cube/glTF/Cube.bin")}},
 			Images:    []Image{{URI: "Cube_BaseColor.png"}, {URI: "Cube_MetallicRoughness.png"}},
-			Materials: []Material{{Name: "Cube", AlphaMode: Opaque, AlphaCutoff: 0.5, PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: [4]float64{1, 1, 1, 1}, MetallicFactor: 1, RoughnessFactor: 1, BaseColorTexture: &TextureInfo{Index: 0}, MetallicRoughnessTexture: &TextureInfo{Index: 1}}}},
-			Meshes:    []Mesh{{Name: "Cube", Primitives: []Primitive{{Indices: 0, Material: 0, Mode: Triangles, Attributes: map[string]uint32{"NORMAL": 2, "POSITION": 1, "TANGENT": 3, "TEXCOORD_0": 4}}}}},
-			Nodes:     []Node{{Mesh: 0, Name: "Cube", Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}}},
+			Materials: []Material{{Name: "Cube", AlphaMode: Opaque, AlphaCutoff: Float64(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: NewRGBA(), MetallicFactor: Float64(1), RoughnessFactor: Float64(1), BaseColorTexture: &TextureInfo{Index: Index(0)}, MetallicRoughnessTexture: &TextureInfo{Index: Index(1)}}}},
+			Meshes:    []Mesh{{Name: "Cube", Primitives: []Primitive{{Indices: Index(0), Material: Index(0), Mode: Triangles, Attributes: map[string]uint32{"NORMAL": 2, "POSITION": 1, "TANGENT": 3, "TEXCOORD_0": 4}}}}},
+			Nodes:     []Node{{Mesh: Index(0), Name: "Cube", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}}},
 			Samplers:  []Sampler{{WrapS: Repeat, WrapT: Repeat}},
-			Scene:     0,
+			Scene:     Index(0),
 			Scenes:    []Scene{{Nodes: []uint32{0}}},
 			Textures: []Texture{
-				{Sampler: 0, Source: 0}, {Sampler: 0, Source: 1},
+				{Sampler: Index(0), Source: Index(0)}, {Sampler: Index(0), Source: Index(1)},
 			},
 		}, false},
 		{args{"testdata/Cameras/glTF/Cameras.gltf", "testdata/Cameras/glTF-Embedded/Cameras.gltf"}, &Document{
 			Accessors: []Accessor{
-				{BufferView: 0, ByteOffset: 0, ComponentType: UnsignedShort, Count: 6, Max: []float64{3}, Min: []float64{0}, Type: Scalar},
-				{BufferView: 1, ByteOffset: 0, ComponentType: Float, Count: 4, Max: []float64{1, 1, 0}, Min: []float64{0, 0, 0}, Type: Vec3},
+				{BufferView: Index(0), ByteOffset: 0, ComponentType: UnsignedShort, Count: 6, Max: []float64{3}, Min: []float64{0}, Type: Scalar},
+				{BufferView: Index(1), ByteOffset: 0, ComponentType: Float, Count: 4, Max: []float64{1, 1, 0}, Min: []float64{0, 0, 0}, Type: Vec3},
 			},
 			Asset: Asset{Version: "2.0"},
 			BufferViews: []BufferView{
@@ -66,25 +66,25 @@ func TestOpen(t *testing.T) {
 			},
 			Buffers: []Buffer{{ByteLength: 60, URI: "simpleSquare.bin", Data: readFile("testdata/Cameras/glTF/simpleSquare.bin")}},
 			Cameras: []Camera{
-				{Type: PerspectiveType, Perspective: &Perspective{AspectRatio: 1.0, Yfov: 0.7, Zfar: 100, Znear: 0.01}},
-				{Type: OrthographicType, Orthographic: &Orthographic{Xmag: 1.0, Ymag: 1.0, Zfar: 100, Znear: 0.01}},
+				{Perspective: &Perspective{AspectRatio: 1.0, Yfov: 0.7, Zfar: 100, Znear: 0.01}},
+				{Orthographic: &Orthographic{Xmag: 1.0, Ymag: 1.0, Zfar: 100, Znear: 0.01}},
 			},
-			Meshes: []Mesh{{Primitives: []Primitive{{Indices: 0, Material: -1, Mode: Triangles, Attributes: map[string]uint32{"POSITION": 1}}}}},
+			Meshes: []Mesh{{Primitives: []Primitive{{Indices: Index(0), Mode: Triangles, Attributes: map[string]uint32{"POSITION": 1}}}}},
 			Nodes: []Node{
-				{Mesh: 0, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{-0.3, 0, 0, 0.9}, Scale: [3]float64{1, 1, 1}},
-				{Mesh: -1, Camera: 0, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}, Translation: [3]float64{0.5, 0.5, 3.0}},
-				{Mesh: -1, Camera: 1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}, Translation: [3]float64{0.5, 0.5, 3.0}},
+				{Mesh: Index(0), Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{-0.3, 0, 0, 0.9}, Scale: [3]float64{1, 1, 1}},
+				{Camera: Index(0), Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}, Translation: [3]float64{0.5, 0.5, 3.0}},
+				{Camera: Index(1), Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}, Translation: [3]float64{0.5, 0.5, 3.0}},
 			},
-			Scene:  -1,
+			Scene:  nil,
 			Scenes: []Scene{{Nodes: []uint32{0, 1, 2}}},
 		}, false},
 		{args{"testdata/BoxVertexColors/glTF-Binary/BoxVertexColors.glb", ""}, &Document{
 			Accessors: []Accessor{
-				{BufferView: 0, ByteOffset: 0, ComponentType: UnsignedShort, Count: 36, Type: Scalar},
-				{BufferView: 1, ByteOffset: 0, ComponentType: Float, Count: 24, Max: []float64{0.5, 0.5, 0.5}, Min: []float64{-0.5, -0.5, -0.5}, Type: Vec3},
-				{BufferView: 2, ByteOffset: 0, ComponentType: Float, Count: 24, Type: Vec3},
-				{BufferView: 3, ByteOffset: 0, ComponentType: Float, Count: 24, Type: Vec4},
-				{BufferView: 4, ByteOffset: 0, ComponentType: Float, Count: 24, Type: Vec2},
+				{BufferView: Index(0), ByteOffset: 0, ComponentType: UnsignedShort, Count: 36, Type: Scalar},
+				{BufferView: Index(1), ByteOffset: 0, ComponentType: Float, Count: 24, Max: []float64{0.5, 0.5, 0.5}, Min: []float64{-0.5, -0.5, -0.5}, Type: Vec3},
+				{BufferView: Index(2), ByteOffset: 0, ComponentType: Float, Count: 24, Type: Vec3},
+				{BufferView: Index(3), ByteOffset: 0, ComponentType: Float, Count: 24, Type: Vec4},
+				{BufferView: Index(4), ByteOffset: 0, ComponentType: Float, Count: 24, Type: Vec2},
 			},
 			Asset: Asset{Version: "2.0", Generator: "FBX2glTF"},
 			BufferViews: []BufferView{
@@ -95,16 +95,16 @@ func TestOpen(t *testing.T) {
 				{Buffer: 0, ByteLength: 192, ByteOffset: 1032, Target: ArrayBuffer},
 			},
 			Buffers:   []Buffer{{ByteLength: 1224, Data: readFile("testdata/BoxVertexColors/glTF-Binary/BoxVertexColors.glb")[1628+20+8:]}},
-			Materials: []Material{{Name: "Default", AlphaMode: Opaque, AlphaCutoff: 0.5, PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: [4]float64{0.8, 0.8, 0.8, 1}, MetallicFactor: 0.1, RoughnessFactor: 0.99}}},
-			Meshes:    []Mesh{{Name: "Cube", Primitives: []Primitive{{Indices: 0, Material: 0, Mode: Triangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}},
+			Materials: []Material{{Name: "Default", AlphaMode: Opaque, AlphaCutoff: Float64(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &RGBA{R: 0.8, G: 0.8, B: 0.8, A: 1}, MetallicFactor: Float64(0.1), RoughnessFactor: Float64(0.99)}}},
+			Meshes:    []Mesh{{Name: "Cube", Primitives: []Primitive{{Indices: Index(0), Material: Index(0), Mode: Triangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}},
 			Nodes: []Node{
-				{Name: "RootNode", Mesh: -1, Camera: -1, Skin: -1, Children: []uint32{1, 2, 3}, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
-				{Name: "Mesh", Mesh: -1, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
-				{Name: "Cube", Mesh: 0, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
-				{Name: "Texture Group", Mesh: -1, Camera: -1, Skin: -1, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
+				{Name: "RootNode", Children: []uint32{1, 2, 3}, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
+				{Name: "Mesh", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
+				{Name: "Cube", Mesh: Index(0), Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
+				{Name: "Texture Group", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
 			},
 			Samplers: []Sampler{{WrapS: Repeat, WrapT: Repeat}},
-			Scene:    0,
+			Scene:    Index(0),
 			Scenes:   []Scene{{Name: "Root Scene", Nodes: []uint32{0}}},
 		}, false},
 	}
diff --git a/encode_test.go b/encode_test.go
index a2ec70f..aa72529 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -49,20 +49,23 @@ func TestEncoder_Encode(t *testing.T) {
 		{"withExtensions", args{&Document{Extras: 8.0, ExtensionsUsed: []string{"c"}, ExtensionsRequired: []string{"d", "e"}}}, false},
 		{"withAsset", args{&Document{Asset: Asset{Extras: 8.0, Copyright: "@2019", Generator: "qmuntal/gltf", Version: "2.0", MinVersion: "1.0"}}}, false},
 		{"withAccessors", args{&Document{Accessors: []Accessor{
-			{Extras: 8.0, Name: "acc_1", BufferView: 0, ByteOffset: 50, ComponentType: Byte, Normalized: true, Count: 5, Type: Vec3, Max: []float64{1, 2}, Min: []float64{2.4}},
-			{BufferView: 0, Normalized: false, Count: 50, Type: Vec4, Sparse: &Sparse{Extras: 8.0, Count: 2,
+			{Extras: 8.0, Name: "acc_1", BufferView: Index(0), ByteOffset: 50, ComponentType: Byte, Normalized: true, Count: 5, Type: Vec3, Max: []float64{1, 2}, Min: []float64{2.4}},
+			{BufferView: Index(0), Normalized: false, Count: 50, Type: Vec4, Sparse: &Sparse{Extras: 8.0, Count: 2,
 				Values:  SparseValues{Extras: 8.0, BufferView: 1, ByteOffset: 2},
 				Indices: SparseIndices{Extras: 8.0, BufferView: 1, ByteOffset: 2, ComponentType: Float}},
 			},
 		}}}, false},
 		{"withAnimations", args{&Document{Animations: []Animation{
 			{Extras: 8.0, Name: "an_1", Channels: []Channel{
-				{Extras: 8.0, Sampler: 1, Target: ChannelTarget{Extras: 8.0, Node: 10, Path: Rotation}},
-				{Extras: 8.0, Sampler: 2, Target: ChannelTarget{Extras: 8.0, Node: 10, Path: Scale}},
+				{Extras: 8.0, Sampler: Index(1), Target: ChannelTarget{Extras: 8.0, Node: Index(10), Path: Rotation}},
+				{Extras: 8.0, Sampler: Index(2), Target: ChannelTarget{Extras: 8.0, Node: Index(10), Path: Scale}},
 			}},
 			{Extras: 8.0, Name: "an_2", Channels: []Channel{
-				{Extras: 8.0, Sampler: 1, Target: ChannelTarget{Extras: 8.0, Node: 3, Path: Weights}},
-				{Extras: 8.0, Sampler: 2, Target: ChannelTarget{Extras: 8.0, Node: 5, Path: Translation}},
+				{Extras: 8.0, Sampler: Index(1), Target: ChannelTarget{Extras: 8.0, Node: Index(3), Path: Weights}},
+				{Extras: 8.0, Sampler: Index(2), Target: ChannelTarget{Extras: 8.0, Node: Index(5), Path: Translation}},
+			}},
+			{Extras: 8.0, Name: "an_3", Samplers: []AnimationSampler{
+				{Extras: 8.0, Input: Index(1), Output: Index(1), Interpolation: CubicSpline},
 			}},
 		}}}, false},
 		{"withBuffer", args{&Document{Buffers: []Buffer{
@@ -75,8 +78,8 @@ func TestEncoder_Encode(t *testing.T) {
 			{Buffer: 10, ByteOffset: 10, ByteLength: 20, ByteStride: 50, Target: ElementArrayBuffer},
 		}}}, false},
 		{"withCameras", args{&Document{Cameras: []Camera{
-			{Extras: 8.0, Name: "cam_1", Type: OrthographicType, Orthographic: &Orthographic{Extras: 8.0, Xmag: 1, Ymag: 2, Zfar: 3, Znear: 4}},
-			{Extras: 8.0, Name: "cam_2", Type: PerspectiveType, Perspective: &Perspective{Extras: 8.0, AspectRatio: 1, Yfov: 2, Zfar: 3, Znear: 4}},
+			{Extras: 8.0, Name: "cam_1", Orthographic: &Orthographic{Extras: 8.0, Xmag: 1, Ymag: 2, Zfar: 3, Znear: 4}},
+			{Extras: 8.0, Name: "cam_2", Perspective: &Perspective{Extras: 8.0, AspectRatio: 1, Yfov: 2, Zfar: 3, Znear: 4}},
 		}}}, false},
 		{"withImages", args{&Document{Images: []Image{
 			{Extras: 8.0, Name: "binary", BufferView: 1, MimeType: "data:image/png"},
@@ -84,51 +87,51 @@ func TestEncoder_Encode(t *testing.T) {
 			{Extras: 8.0, Name: "external", URI: "https://web.com/a", MimeType: "data:image/png"},
 		}}}, false},
 		{"withMaterials", args{&Document{Materials: []Material{
-			{Extras: 8.0, Name: "base", EmissiveFactor: [3]float64{1.0, 1.0, 1.0}, DoubleSided: true, AlphaCutoff: 0.5, AlphaMode: Opaque},
-			{Extras: 8.0, Name: "pbr", AlphaCutoff: 0.5, AlphaMode: Opaque,
+			{Extras: 8.0, Name: "base", EmissiveFactor: [3]float64{1.0, 1.0, 1.0}, DoubleSided: true, AlphaCutoff: Float64(0.5), AlphaMode: Opaque},
+			{Extras: 8.0, Name: "pbr", AlphaCutoff: Float64(0.5), AlphaMode: Opaque,
 				PBRMetallicRoughness: &PBRMetallicRoughness{
-					Extras: 8.0, MetallicFactor: 1, RoughnessFactor: 2, BaseColorFactor: [4]float64{1, 2, 3, 4},
-					BaseColorTexture:         &TextureInfo{Extras: 8.0, Index: 1, TexCoord: 3},
-					MetallicRoughnessTexture: &TextureInfo{Extras: 8.0, Index: 6, TexCoord: 5},
+					Extras: 8.0, MetallicFactor: Float64(1), RoughnessFactor: Float64(2), BaseColorFactor: &RGBA{R: 0.8, G: 0.8, B: 0.8, A: 1},
+					BaseColorTexture:         &TextureInfo{Extras: 8.0, Index: Index(1), TexCoord: 3},
+					MetallicRoughnessTexture: &TextureInfo{Extras: 8.0, Index: Index(6), TexCoord: 5},
 				},
 			},
-			{Extras: 8.0, Name: "normal", AlphaCutoff: 0.7, AlphaMode: Blend,
-				NormalTexture: &NormalTexture{Extras: 8.0, Index: 1, TexCoord: 2, Scale: 2.0},
+			{Extras: 8.0, Name: "normal", AlphaCutoff: Float64(0.7), AlphaMode: Blend,
+				NormalTexture: &NormalTexture{Extras: 8.0, Index: Index(1), TexCoord: 2, Scale: Float64(2.0)},
 			},
-			{Extras: 8.0, Name: "occlusion", AlphaCutoff: 0.5, AlphaMode: Mask,
-				OcclusionTexture: &OcclusionTexture{Extras: 8.0, Index: 1, TexCoord: 2, Strength: 2.0},
+			{Extras: 8.0, Name: "occlusion", AlphaCutoff: Float64(0.5), AlphaMode: Mask,
+				OcclusionTexture: &OcclusionTexture{Extras: 8.0, Index: Index(1), TexCoord: 2, Strength: Float64(2.0)},
 			},
-			{Extras: 8.0, Name: "emmisice", AlphaCutoff: 0.5, AlphaMode: Mask, EmissiveTexture: &TextureInfo{Extras: 8.0, Index: 4, TexCoord: 50}},
+			{Extras: 8.0, Name: "emmisice", AlphaCutoff: Float64(0.5), AlphaMode: Mask, EmissiveTexture: &TextureInfo{Extras: 8.0, Index: Index(4), TexCoord: 50}},
 		}}}, false},
 		{"withMeshes", args{&Document{Meshes: []Mesh{
 			{Extras: 8.0, Name: "mesh_1", Weights: []float64{1.2, 2}},
 			{Extras: 8.0, Name: "mesh_2", Primitives: []Primitive{
-				{Extras: 8.0, Attributes: Attribute{"POSITION": 1}, Indices: 2, Material: 1, Mode: Lines},
-				{Extras: 8.0, Targets: []Attribute{{"POSITION": 1, "THEN": 4}, {"OTHER": 2}}, Indices: 2, Material: 1, Mode: Lines},
+				{Extras: 8.0, Attributes: Attribute{"POSITION": 1}, Indices: Index(2), Material: Index(1), Mode: Lines},
+				{Extras: 8.0, Targets: []Attribute{{"POSITION": 1, "THEN": 4}, {"OTHER": 2}}, Indices: Index(2), Material: Index(1), Mode: Lines},
 			}},
 		}}}, false},
 		{"withNodes", args{&Document{Nodes: []Node{
-			{Extras: 8.0, Name: "n-1", Camera: 1, Children: []uint32{1, 2}, Skin: 3,
-				Matrix: [16]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, Mesh: 15, Rotation: [4]float64{1.5, 1.3, 12, 0}, Scale: [3]float64{1, 3, 4}, Translation: [3]float64{0, 7.8, 9}, Weights: []float64{1, 3}},
-			{Extras: 8.0, Name: "n-2", Camera: 1, Children: []uint32{1, 2}, Skin: 3,
-				Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Mesh: 15, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
+			{Extras: 8.0, Name: "n-1", Camera: Index(1), Children: []uint32{1, 2}, Skin: Index(3),
+				Matrix: [16]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, Mesh: Index(15), Rotation: [4]float64{1.5, 1.3, 12, 0}, Scale: [3]float64{1, 3, 4}, Translation: [3]float64{0, 7.8, 9}, Weights: []float64{1, 3}},
+			{Extras: 8.0, Name: "n-2", Camera: Index(1), Children: []uint32{1, 2}, Skin: Index(3),
+				Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Mesh: Index(15), Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
 		}}}, false},
 		{"withSampler", args{&Document{Samplers: []Sampler{
 			{Extras: 8.0, Name: "s_1", MagFilter: MagLinear, MinFilter: MinLinearMipMapLinear, WrapS: ClampToEdge, WrapT: MirroredRepeat},
 			{Extras: 8.0, Name: "s_2", MagFilter: MagNearest, MinFilter: MinLinearMipMapLinear, WrapS: MirroredRepeat, WrapT: Repeat},
 		}}}, false},
-		{"withScene", args{&Document{Scene: 1}}, false},
+		{"withScene", args{&Document{Scene: Index(1)}}, false},
 		{"withScenes", args{&Document{Scenes: []Scene{
 			{Extras: 8.0, Name: "s_1", Nodes: []uint32{1, 2}},
 			{Extras: 8.0, Name: "s_2", Nodes: []uint32{2, 3}},
 		}}}, false},
 		{"withSkins", args{&Document{Skins: []Skin{
-			{Extras: 8.0, Name: "skin_1", InverseBindMatrices: 2, Skeleton: 4, Joints: []uint32{5, 6}},
-			{Extras: 8.0, Name: "skin_2", InverseBindMatrices: 3, Skeleton: 4, Joints: []uint32{7, 8}},
+			{Extras: 8.0, Name: "skin_1", InverseBindMatrices: Index(2), Skeleton: Index(4), Joints: []uint32{5, 6}},
+			{Extras: 8.0, Name: "skin_2", InverseBindMatrices: Index(3), Skeleton: Index(4), Joints: []uint32{7, 8}},
 		}}}, false},
 		{"withTextures", args{&Document{Textures: []Texture{
-			{Extras: 8.0, Name: "t_1", Sampler: 2, Source: 3},
-			{Extras: 8.0, Name: "t_2", Sampler: 3, Source: 4},
+			{Extras: 8.0, Name: "t_1", Sampler: Index(2), Source: Index(3)},
+			{Extras: 8.0, Name: "t_2", Sampler: Index(3), Source: Index(4)},
 		}}}, false},
 	}
 	for _, tt := range tests {
diff --git a/extensions_test.go b/extensions_test.go
deleted file mode 100644
index bd52c0e..0000000
--- a/extensions_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package gltf
-
-import (
-	"reflect"
-	"testing"
-)
-
-func TestPBRSpecularGlossiness_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		p       *PBRSpecularGlossiness
-		args    args
-		want    *PBRSpecularGlossiness
-		wantErr bool
-	}{
-		{"default", new(PBRSpecularGlossiness), args{[]byte("{}")}, &PBRSpecularGlossiness{DiffuseFactor: [4]float64{1, 1, 1, 1}, SpecularFactor: [3]float64{1, 1, 1}, GlossinessFactor: 1}, false},
-		{"nodefault", new(PBRSpecularGlossiness), args{[]byte(`{"diffuseFactor": [0.1,0.2,0.3,0.4],"specularFactor":[0.5,0.6,0.7],"glossinessFactor":0.5}`)}, &PBRSpecularGlossiness{
-			DiffuseFactor: [4]float64{0.1, 0.2, 0.3, 0.4}, SpecularFactor: [3]float64{0.5, 0.6, 0.7}, GlossinessFactor: 0.5,
-		}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.p.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("PBRSpecularGlossiness.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.p, tt.want) {
-				t.Errorf("PBRSpecularGlossiness.UnmarshalJSON() = %v, want %v", tt.p, tt.want)
-			}
-		})
-	}
-}
-
-func TestPBRSpecularGlossiness_MarshalJSON(t *testing.T) {
-	tests := []struct {
-		name    string
-		p       *PBRSpecularGlossiness
-		want    []byte
-		wantErr bool
-	}{
-		{"default", &PBRSpecularGlossiness{GlossinessFactor: 1, DiffuseFactor: [4]float64{1, 1, 1, 1}, SpecularFactor: [3]float64{1, 1, 1}}, []byte(`{}`), false},
-		{"empty", &PBRSpecularGlossiness{GlossinessFactor: 0, DiffuseFactor: [4]float64{0, 0, 0, 0}, SpecularFactor: [3]float64{0, 0, 0}}, []byte(`{"diffuseFactor":[0,0,0,0],"specularFactor":[0,0,0],"glossinessFactor":0}`), false},
-		{"nodefault", &PBRSpecularGlossiness{GlossinessFactor: 0.5, DiffuseFactor: [4]float64{1, 0.5, 1, 1}, SpecularFactor: [3]float64{1, 1, 0.5}}, []byte(`{"diffuseFactor":[1,0.5,1,1],"specularFactor":[1,1,0.5],"glossinessFactor":0.5}`), false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.p.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("PBRSpecularGlossiness.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("PBRSpecularGlossiness.MarshalJSON() = %v, want %v", string(got), string(tt.want))
-			}
-		})
-	}
-}
-
-func TestExtensions_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    *Extensions
-		wantErr bool
-	}{
-		{"specularGlossiness", args{[]byte(`{"KHR_materials_pbrSpecularGlossiness": {"diffuseFactor":[1,0.5,1,1],"specularFactor":[1,1,0.5],"glossinessFactor":0.5}}`)}, &Extensions{
-			ExtPBRSpecularGlossiness: &PBRSpecularGlossiness{GlossinessFactor: 0.5, DiffuseFactor: [4]float64{1, 0.5, 1, 1}, SpecularFactor: [3]float64{1, 1, 0.5}},
-		}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ext := Extensions{}
-			if err := ext.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Extensions.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(&ext, tt.want) {
-				t.Errorf("PBRSpecularGlossiness.MarshalJSON() = %v, want %v", &ext, tt.want)
-			}
-		})
-	}
-}
diff --git a/specular/specular_test.go b/specular/specular_test.go
new file mode 100644
index 0000000..1dd0a12
--- /dev/null
+++ b/specular/specular_test.go
@@ -0,0 +1,79 @@
+package specular
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"github.com/qmuntal/gltf"
+)
+
+func TestPBRSpecularGlossiness_UnmarshalJSON(t *testing.T) {
+	type args struct {
+		data []byte
+	}
+	tests := []struct {
+		name    string
+		p       *PBRSpecularGlossiness
+		args    args
+		want    *PBRSpecularGlossiness
+		wantErr bool
+	}{
+		{"default", new(PBRSpecularGlossiness), args{[]byte("{}")}, &PBRSpecularGlossiness{DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB(), GlossinessFactor: gltf.Float64(1)}, false},
+		{"nodefault", new(PBRSpecularGlossiness), args{[]byte(`{"diffuseFactor": [0.1,0.2,0.3,0.4],"specularFactor":[0.5,0.6,0.7],"glossinessFactor":0.5}`)}, &PBRSpecularGlossiness{
+			DiffuseFactor: &gltf.RGBA{R: 0.1, G: 0.2, B: 0.3, A: 0.4}, SpecularFactor: &gltf.RGB{R: 0.5, G: 0.6, B: 0.7}, GlossinessFactor: gltf.Float64(0.5),
+		}, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.p.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
+				t.Errorf("PBRSpecularGlossiness.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(tt.p, tt.want) {
+				t.Errorf("PBRSpecularGlossiness.UnmarshalJSON() = %v, want %v", tt.p, tt.want)
+			}
+		})
+	}
+}
+
+func TestPBRSpecularGlossiness_MarshalJSON(t *testing.T) {
+	tests := []struct {
+		name    string
+		p       *PBRSpecularGlossiness
+		want    []byte
+		wantErr bool
+	}{
+		{"default", &PBRSpecularGlossiness{GlossinessFactor: gltf.Float64(1), DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB()}, []byte(`{}`), false},
+		{"empty", &PBRSpecularGlossiness{}, []byte(`{}`), false},
+		{"nodefault", &PBRSpecularGlossiness{GlossinessFactor: gltf.Float64(0.5), DiffuseFactor: &gltf.RGBA{R: 1, G: 0.5, B: 1, A: 1}, SpecularFactor: &gltf.RGB{R: 1, G: 1, B: 0.5}}, []byte(`{"diffuseFactor":[1,0.5,1,1],"specularFactor":[1,1,0.5],"glossinessFactor":0.5}`), false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.p.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("PBRSpecularGlossiness.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PBRSpecularGlossiness.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			}
+		})
+	}
+}
+
+func TestNew(t *testing.T) {
+	tests := []struct {
+		name string
+		want json.Unmarshaler
+	}{
+		{"base", new(PBRSpecularGlossiness)},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := New(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("New() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/struct_test.go b/struct_test.go
index 2bb74fc..168a403 100644
--- a/struct_test.go
+++ b/struct_test.go
@@ -1,6 +1,7 @@
 package gltf
 
 import (
+	"encoding/json"
 	"reflect"
 	"testing"
 )
@@ -103,25 +104,22 @@ func TestNode_UnmarshalJSON(t *testing.T) {
 			Matrix:   [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
 			Rotation: [4]float64{0, 0, 0, 1},
 			Scale:    [3]float64{1, 1, 1},
-			Camera:   -1,
-			Mesh:     -1,
-			Skin:     -1,
 		}, false},
 		{"nodefault", new(Node), args{[]byte(`{"matrix":[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],"rotation":[0,0,0,1],"scale":[1,1,1],"camera":0,"mesh":0,"skin":0}`)}, &Node{
 			Matrix:   [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
 			Rotation: [4]float64{0, 0, 0, 1},
 			Scale:    [3]float64{1, 1, 1},
-			Camera:   0,
-			Mesh:     0,
-			Skin:     0,
+			Camera:   Index(0),
+			Mesh:     Index(0),
+			Skin:     Index(0),
 		}, false},
 		{"nodefault", new(Node), args{[]byte(`{"matrix":[1,2,2,0,0,1,3,4,0,0,1,0,5,0,0,5],"rotation":[1,2,3,4],"scale":[2,3,4],"camera":1,"mesh":2,"skin":3}`)}, &Node{
 			Matrix:   [16]float64{1, 2, 2, 0, 0, 1, 3, 4, 0, 0, 1, 0, 5, 0, 0, 5},
 			Rotation: [4]float64{1, 2, 3, 4},
 			Scale:    [3]float64{2, 3, 4},
-			Camera:   1,
-			Mesh:     2,
-			Skin:     3,
+			Camera:   Index(1),
+			Mesh:     Index(2),
+			Skin:     Index(3),
 		}, false},
 	}
 	for _, tt := range tests {
@@ -137,34 +135,6 @@ func TestNode_UnmarshalJSON(t *testing.T) {
 	}
 }
 
-func TestPrimitive_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		p       *Primitive
-		args    args
-		want    *Primitive
-		wantErr bool
-	}{
-		{"default", new(Primitive), args{[]byte("{}")}, &Primitive{Mode: Triangles, Indices: -1, Material: -1}, false},
-		{"empty", new(Primitive), args{[]byte(`{"mode": 0, "indices": 0, "material": 0}`)}, &Primitive{Mode: Points, Indices: 0, Material: 0}, false},
-		{"nodefault", new(Primitive), args{[]byte(`{"mode": 1, "indices": 2, "material": 3}`)}, &Primitive{Mode: Lines, Indices: 2, Material: 3}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.p.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Primitive.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.p, tt.want) {
-				t.Errorf("Primitive.UnmarshalJSON() = %v, want %v", tt.p, tt.want)
-			}
-		})
-	}
-}
-
 func TestMaterial_UnmarshalJSON(t *testing.T) {
 	type args struct {
 		data []byte
@@ -176,8 +146,8 @@ func TestMaterial_UnmarshalJSON(t *testing.T) {
 		want    *Material
 		wantErr bool
 	}{
-		{"default", new(Material), args{[]byte("{}")}, &Material{AlphaCutoff: 0.5, AlphaMode: Opaque}, false},
-		{"nodefault", new(Material), args{[]byte(`{"alphaCutoff": 0.2, "alphaMode": "MASK"}`)}, &Material{AlphaCutoff: 0.2, AlphaMode: Mask}, false},
+		{"default", new(Material), args{[]byte("{}")}, &Material{AlphaCutoff: Float64(0.5), AlphaMode: Opaque}, false},
+		{"nodefault", new(Material), args{[]byte(`{"alphaCutoff": 0.2, "alphaMode": "MASK"}`)}, &Material{AlphaCutoff: Float64(0.2), AlphaMode: Mask}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -203,9 +173,9 @@ func TestNormalTexture_UnmarshalJSON(t *testing.T) {
 		want    *NormalTexture
 		wantErr bool
 	}{
-		{"default", new(NormalTexture), args{[]byte("{}")}, &NormalTexture{Scale: 1, Index: -1}, false},
-		{"empty", new(NormalTexture), args{[]byte(`{"scale": 0, "index": 0}`)}, &NormalTexture{Scale: 0, Index: 0}, false},
-		{"nodefault", new(NormalTexture), args{[]byte(`{"scale": 0.5, "index":2}`)}, &NormalTexture{Scale: 0.5, Index: 2}, false},
+		{"default", new(NormalTexture), args{[]byte("{}")}, &NormalTexture{Scale: Float64(1)}, false},
+		{"empty", new(NormalTexture), args{[]byte(`{"scale": 0, "index": 0}`)}, &NormalTexture{Scale: Float64(0), Index: Index(0)}, false},
+		{"nodefault", new(NormalTexture), args{[]byte(`{"scale": 0.5, "index":2}`)}, &NormalTexture{Scale: Float64(0.5), Index: Index(2)}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -231,9 +201,9 @@ func TestOcclusionTexture_UnmarshalJSON(t *testing.T) {
 		want    *OcclusionTexture
 		wantErr bool
 	}{
-		{"default", new(OcclusionTexture), args{[]byte("{}")}, &OcclusionTexture{Strength: 1, Index: -1}, false},
-		{"empty", new(OcclusionTexture), args{[]byte(`{"strength": 0, "index": 0}`)}, &OcclusionTexture{Strength: 0, Index: 0}, false},
-		{"nodefault", new(OcclusionTexture), args{[]byte(`{"strength": 0.5, "index":2}`)}, &OcclusionTexture{Strength: 0.5, Index: 2}, false},
+		{"default", new(OcclusionTexture), args{[]byte("{}")}, &OcclusionTexture{Strength: Float64(1)}, false},
+		{"empty", new(OcclusionTexture), args{[]byte(`{"strength": 0, "index": 0}`)}, &OcclusionTexture{Strength: Float64(0), Index: Index(0)}, false},
+		{"nodefault", new(OcclusionTexture), args{[]byte(`{"strength": 0.5, "index":2}`)}, &OcclusionTexture{Strength: Float64(0.5), Index: Index(2)}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -259,9 +229,9 @@ func TestPBRMetallicRoughness_UnmarshalJSON(t *testing.T) {
 		want    *PBRMetallicRoughness
 		wantErr bool
 	}{
-		{"default", new(PBRMetallicRoughness), args{[]byte("{}")}, &PBRMetallicRoughness{BaseColorFactor: [4]float64{1, 1, 1, 1}, MetallicFactor: 1, RoughnessFactor: 1}, false},
+		{"default", new(PBRMetallicRoughness), args{[]byte("{}")}, &PBRMetallicRoughness{BaseColorFactor: NewRGBA(), MetallicFactor: Float64(1), RoughnessFactor: Float64(1)}, false},
 		{"nodefault", new(PBRMetallicRoughness), args{[]byte(`{"baseColorFactor": [0.1,0.2,0.6,0.7],"metallicFactor":0.5,"roughnessFactor":0.1}`)}, &PBRMetallicRoughness{
-			BaseColorFactor: [4]float64{0.1, 0.2, 0.6, 0.7}, MetallicFactor: 0.5, RoughnessFactor: 0.1,
+			BaseColorFactor: &RGBA{R: 0.1, G: 0.2, B: 0.6, A: 0.7}, MetallicFactor: Float64(0.5), RoughnessFactor: Float64(0.1),
 		}, false},
 	}
 	for _, tt := range tests {
@@ -277,654 +247,433 @@ func TestPBRMetallicRoughness_UnmarshalJSON(t *testing.T) {
 	}
 }
 
-func TestSampler_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		s       *Sampler
-		args    args
-		want    *Sampler
-		wantErr bool
-	}{
-		{"default", new(Sampler), args{[]byte("{}")}, &Sampler{WrapS: Repeat, WrapT: Repeat}, false},
-		{"nodefault", new(Sampler), args{[]byte(`{"wrapS": 33648, "wrapT": 33071}`)}, &Sampler{WrapS: MirroredRepeat, WrapT: ClampToEdge}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.s.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Sampler.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.s, tt.want) {
-				t.Errorf("Sampler.UnmarshalJSON() = %v, want %v", tt.s, tt.want)
-			}
-		})
-	}
-}
-
-func TestAnimationSampler_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		as      *AnimationSampler
-		args    args
-		want    *AnimationSampler
-		wantErr bool
-	}{
-		{"default", new(AnimationSampler), args{[]byte("{}")}, &AnimationSampler{Interpolation: Linear, Input: -1, Output: -1}, false},
-		{"empty", new(AnimationSampler), args{[]byte(`{"interpolation": "LINEAR", "input": 0, "output":0}`)}, &AnimationSampler{Interpolation: Linear, Input: 0, Output: 0}, false},
-		{"nodefault", new(AnimationSampler), args{[]byte(`{"interpolation": "STEP", "input": 1, "output":2}`)}, &AnimationSampler{Interpolation: Step, Input: 1, Output: 2}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.as.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("AnimationSampler.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.as, tt.want) {
-				t.Errorf("AnimationSampler.UnmarshalJSON() = %v, want %v", tt.as, tt.want)
-			}
-		})
-	}
-}
-
-func TestAccessor_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		a       *Accessor
-		args    args
-		want    *Accessor
-		wantErr bool
-	}{
-		{"default", new(Accessor), args{[]byte("{}")}, &Accessor{BufferView: -1}, false},
-		{"empty", new(Accessor), args{[]byte(`{"bufferView": 0}`)}, &Accessor{BufferView: 0}, false},
-		{"nodefault", new(Accessor), args{[]byte(`{"bufferView": 1}`)}, &Accessor{BufferView: 1}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.a.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Accessor.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.a, tt.want) {
-				t.Errorf("Accessor.UnmarshalJSON() = %v, want %v", tt.a, tt.want)
-			}
-		})
-	}
-}
-
-func TestChannelTarget_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		ch      *ChannelTarget
-		args    args
-		want    *ChannelTarget
-		wantErr bool
-	}{
-		{"default", new(ChannelTarget), args{[]byte("{}")}, &ChannelTarget{Node: -1}, false},
-		{"empty", new(ChannelTarget), args{[]byte(`{"node": 0}`)}, &ChannelTarget{Node: 0}, false},
-		{"nodefault", new(ChannelTarget), args{[]byte(`{"node": 1}`)}, &ChannelTarget{Node: 1}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.ch.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("ChannelTarget.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.ch, tt.want) {
-				t.Errorf("ChannelTarget.UnmarshalJSON() = %v, want %v", tt.ch, tt.want)
-			}
-		})
-	}
-}
-
-func TestChannel_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		ch      *Channel
-		args    args
-		want    *Channel
-		wantErr bool
-	}{
-		{"default", new(Channel), args{[]byte("{}")}, &Channel{Sampler: -1}, false},
-		{"empty", new(Channel), args{[]byte(`{"sampler": 0}`)}, &Channel{Sampler: 0}, false},
-		{"nodefault", new(Channel), args{[]byte(`{"sampler": 1}`)}, &Channel{Sampler: 1}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.ch.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Channel.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.ch, tt.want) {
-				t.Errorf("Channel.UnmarshalJSON() = %v, want %v", tt.ch, tt.want)
-			}
-		})
-	}
-}
-
-func TestBufferView_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
-	tests := []struct {
-		name    string
-		b       *BufferView
-		args    args
-		want    *BufferView
-		wantErr bool
-	}{
-		{"default", new(BufferView), args{[]byte("{}")}, &BufferView{Buffer: -1}, false},
-		{"empty", new(BufferView), args{[]byte(`{"Buffer": 0}`)}, &BufferView{Buffer: 0}, false},
-		{"nodefault", new(BufferView), args{[]byte(`{"Buffer": 1}`)}, &BufferView{Buffer: 1}, false},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.b.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("BufferView.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(tt.b, tt.want) {
-				t.Errorf("BufferView.UnmarshalJSON() = %v, want %v", tt.b, tt.want)
-			}
-		})
-	}
-}
-
-func TestTextureInfo_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
+func TestNode_MarshalJSON(t *testing.T) {
 	tests := []struct {
 		name    string
-		t       *TextureInfo
-		args    args
-		want    *TextureInfo
+		n       *Node
+		want    []byte
 		wantErr bool
 	}{
-		{"default", new(TextureInfo), args{[]byte("{}")}, &TextureInfo{Index: -1}, false},
-		{"empty", new(TextureInfo), args{[]byte(`{"index": 0}`)}, &TextureInfo{Index: 0}, false},
-		{"nodefault", new(TextureInfo), args{[]byte(`{"index": 1}`)}, &TextureInfo{Index: 1}, false},
+		{"default", &Node{
+			Matrix:   [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
+			Rotation: [4]float64{0, 0, 0, 1},
+			Scale:    [3]float64{1, 1, 1},
+		}, []byte("{}"), false},
+		{"default2", &Node{
+			Matrix:   [16]float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			Rotation: [4]float64{0, 0, 0, 0},
+			Scale:    [3]float64{0, 0, 0},
+		}, []byte("{}"), false},
+		{"empty", &Node{
+			Matrix:   [16]float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			Rotation: [4]float64{0, 0, 0, 0},
+			Scale:    [3]float64{0, 0, 0},
+			Camera:   Index(0),
+			Skin:     Index(0),
+			Mesh:     Index(0),
+		}, []byte(`{"camera":0,"skin":0,"mesh":0}`), false},
+		{"nodefault", &Node{
+			Matrix:      [16]float64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+			Rotation:    [4]float64{1, 0, 0, 0},
+			Scale:       [3]float64{1, 0, 0},
+			Translation: [3]float64{1, 0, 0},
+			Camera:      Index(1),
+			Skin:        Index(1),
+			Mesh:        Index(1),
+		}, []byte(`{"camera":1,"skin":1,"matrix":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"mesh":1,"rotation":[1,0,0,0],"scale":[1,0,0],"translation":[1,0,0]}`), false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.t.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("TextureInfo.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			got, err := tt.n.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Node.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(tt.t, tt.want) {
-				t.Errorf("TextureInfo.UnmarshalJSON() = %v, want %v", tt.t, tt.want)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Node.MarshalJSON() = %v, want %v", string(got), string(tt.want))
 			}
 		})
 	}
 }
 
-func TestSkin_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
+func TestMaterial_MarshalJSON(t *testing.T) {
 	tests := []struct {
 		name    string
-		s       *Skin
-		args    args
-		want    *Skin
+		m       *Material
+		want    []byte
 		wantErr bool
 	}{
-		{"default", new(Skin), args{[]byte("{}")}, &Skin{InverseBindMatrices: -1, Skeleton: -1}, false},
-		{"empty", new(Skin), args{[]byte(`{"InverseBindMatrices": 0, "skeleton": 0}`)}, &Skin{InverseBindMatrices: 0, Skeleton: 0}, false},
-		{"nodefault", new(Skin), args{[]byte(`{"InverseBindMatrices": 1, "skeleton": 2}`)}, &Skin{InverseBindMatrices: 1, Skeleton: 2}, false},
+		{"default", &Material{AlphaCutoff: Float64(0.5), AlphaMode: Opaque}, []byte(`{}`), false},
+		{"empty", &Material{AlphaMode: Blend}, []byte(`{"alphaMode":"BLEND"}`), false},
+		{"nodefault", &Material{AlphaCutoff: Float64(1), AlphaMode: Blend}, []byte(`{"alphaMode":"BLEND","alphaCutoff":1}`), false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.s.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Skin.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			got, err := tt.m.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Material.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(tt.s, tt.want) {
-				t.Errorf("Skin.UnmarshalJSON() = %v, want %v", tt.s, tt.want)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Material.MarshalJSON() = %v, want %v", string(got), string(tt.want))
 			}
 		})
 	}
 }
 
-func TestTexture_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
+func TestNormalTexture_MarshalJSON(t *testing.T) {
 	tests := []struct {
 		name    string
-		t       *Texture
-		args    args
-		want    *Texture
+		n       *NormalTexture
+		want    []byte
 		wantErr bool
 	}{
-		{"default", new(Texture), args{[]byte("{}")}, &Texture{Sampler: -1, Source: -1}, false},
-		{"empty", new(Texture), args{[]byte(`{"sampler": 0, "source": 0}`)}, &Texture{Sampler: 0, Source: 0}, false},
-		{"nodefault", new(Texture), args{[]byte(`{"sampler": 1, "source": 2}`)}, &Texture{Sampler: 1, Source: 2}, false},
+		{"default", &NormalTexture{Scale: Float64(1)}, []byte(`{}`), false},
+		{"empty", &NormalTexture{Index: Index(0)}, []byte(`{"index":0}`), false},
+		{"nodefault", &NormalTexture{Index: Index(1), Scale: Float64(0.5)}, []byte(`{"index":1,"scale":0.5}`), false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.t.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Texture.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			got, err := tt.n.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NormalTexture.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(tt.t, tt.want) {
-				t.Errorf("Texture.UnmarshalJSON() = %v, want %v", tt.t, tt.want)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NormalTexture.MarshalJSON() = %v, want %v", string(got), string(tt.want))
 			}
 		})
 	}
 }
 
-func TestDocument_UnmarshalJSON(t *testing.T) {
-	type args struct {
-		data []byte
-	}
+func TestOcclusionTexture_MarshalJSON(t *testing.T) {
 	tests := []struct {
 		name    string
-		d       *Document
-		args    args
-		want    *Document
+		o       *OcclusionTexture
+		want    []byte
 		wantErr bool
 	}{
-		{"default", new(Document), args{[]byte("{}")}, &Document{Scene: -1}, false},
-		{"empty", new(Document), args{[]byte(`{"scene": 0}`)}, &Document{Scene: 0}, false},
-		{"nodefault", new(Document), args{[]byte(`{"scene": 1}`)}, &Document{Scene: 1}, false},
+		{"default", &OcclusionTexture{Strength: Float64(1)}, []byte(`{}`), false},
+		{"empty", &OcclusionTexture{Index: Index(0)}, []byte(`{"index":0}`), false},
+		{"nodefault", &OcclusionTexture{Index: Index(1), Strength: Float64(0.5)}, []byte(`{"index":1,"strength":0.5}`), false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if err := tt.d.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
-				t.Errorf("Document.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			got, err := tt.o.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("OcclusionTexture.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(tt.d, tt.want) {
-				t.Errorf("Document.UnmarshalJSON() = %v, want %v", tt.d, tt.want)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("OcclusionTexture.MarshalJSON() = %v, want %v", string(got), string(tt.want))
 			}
 		})
 	}
 }
 
-func TestDocument_MarshalJSON(t *testing.T) {
+func TestPBRMetallicRoughness_MarshalJSON(t *testing.T) {
 	tests := []struct {
 		name    string
-		d       *Document
+		p       *PBRMetallicRoughness
 		want    []byte
 		wantErr bool
 	}{
-		{"default", &Document{Scene: -1}, []byte(`{"asset":{"version":""}}`), false},
-		{"empty", &Document{Scene: 0}, []byte(`{"asset":{"version":""},"scene":0}`), false},
-		{"nodefault", &Document{Scene: 1}, []byte(`{"asset":{"version":""},"scene":1}`), false},
+		{"default", &PBRMetallicRoughness{MetallicFactor: Float64(1), RoughnessFactor: Float64(1), BaseColorFactor: NewRGBA()}, []byte(`{}`), false},
+		{"empty", &PBRMetallicRoughness{MetallicFactor: Float64(0), RoughnessFactor: Float64(0)}, []byte(`{"metallicFactor":0,"roughnessFactor":0}`), false},
+		{"nodefault", &PBRMetallicRoughness{MetallicFactor: Float64(0.5), RoughnessFactor: Float64(0.5), BaseColorFactor: &RGBA{R: 1, G: 0.5, B: 1, A: 1}}, []byte(`{"baseColorFactor":[1,0.5,1,1],"metallicFactor":0.5,"roughnessFactor":0.5}`), false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.d.MarshalJSON()
+			got, err := tt.p.MarshalJSON()
 			if (err != nil) != tt.wantErr {
-				t.Errorf("Document.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("PBRMetallicRoughness.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Document.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+				t.Errorf("PBRMetallicRoughness.MarshalJSON() = %v, want %v", string(got), string(tt.want))
 			}
 		})
 	}
 }
 
-func TestAccessor_MarshalJSON(t *testing.T) {
+func TestBuffer_marshalData(t *testing.T) {
 	tests := []struct {
 		name    string
-		a       *Accessor
-		want    []byte
+		b       *Buffer
+		want    []uint8
 		wantErr bool
 	}{
-		{"default", &Accessor{BufferView: -1}, []byte(`{"componentType":0,"count":0,"type":""}`), false},
-		{"empty", &Accessor{BufferView: 0}, []byte(`{"bufferView":0,"componentType":0,"count":0,"type":""}`), false},
-		{"nodefault", &Accessor{BufferView: 1}, []byte(`{"bufferView":1,"componentType":0,"count":0,"type":""}`), false},
+		{"error", &Buffer{URI: "data:application/octet-stream;base64,_"}, []uint8{}, true},
+		{"external", &Buffer{URI: "http://web.com"}, []uint8{}, false},
+		{"empty", &Buffer{URI: "data:application/octet-stream;base64,"}, []uint8{}, false},
+		{"test", &Buffer{URI: "data:application/octet-stream;base64,TEST"}, []uint8{76, 68, 147}, false},
+		{"complex", &Buffer{URI: "data:application/octet-stream;base64,YW55IGNhcm5hbCBwbGVhcw=="}, []uint8{97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.a.MarshalJSON()
+			got, err := tt.b.marshalData()
 			if (err != nil) != tt.wantErr {
-				t.Errorf("Accessor.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("Buffer.marshalData() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Accessor.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+				t.Errorf("Buffer.marshalData() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestBufferView_MarshalJSON(t *testing.T) {
+func TestCamera_MarshalJSON(t *testing.T) {
 	tests := []struct {
 		name    string
-		b       *BufferView
+		c       *Camera
 		want    []byte
 		wantErr bool
 	}{
-		{"default", &BufferView{Buffer: -1}, []byte(`{"byteLength":0}`), false},
-		{"empty", &BufferView{Buffer: 0}, []byte(`{"buffer":0,"byteLength":0}`), false},
-		{"nodefault", &BufferView{Buffer: 1}, []byte(`{"buffer":1,"byteLength":0}`), false},
+		{"empty", &Camera{}, nil, true},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.b.MarshalJSON()
+			got, err := tt.c.MarshalJSON()
 			if (err != nil) != tt.wantErr {
-				t.Errorf("BufferView.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("Camera.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("BufferView.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+				t.Errorf("Camera.MarshalJSON() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestNode_MarshalJSON(t *testing.T) {
+func TestNode_MatrixOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		n       *Node
-		want    []byte
-		wantErr bool
+		name string
+		n    *Node
+		want [16]float64
 	}{
-		{"default", &Node{
-			Matrix:   [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
-			Rotation: [4]float64{0, 0, 0, 1},
-			Scale:    [3]float64{1, 1, 1},
-			Camera:   -1,
-			Skin:     -1,
-			Mesh:     -1,
-		}, []byte("{}"), false},
-		{"empty", &Node{
-			Matrix:   [16]float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			Rotation: [4]float64{0, 0, 0, 0},
-			Scale:    [3]float64{0, 0, 0},
-			Camera:   0,
-			Skin:     0,
-			Mesh:     0,
-		}, []byte(`{"camera":0,"skin":0,"matrix":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"mesh":0,"rotation":[0,0,0,0],"scale":[0,0,0]}`), false},
-		{"nodefault", &Node{
-			Matrix:      [16]float64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			Rotation:    [4]float64{1, 0, 0, 0},
-			Scale:       [3]float64{1, 0, 0},
-			Translation: [3]float64{1, 0, 0},
-			Camera:      1,
-			Skin:        1,
-			Mesh:        1,
-		}, []byte(`{"camera":1,"skin":1,"matrix":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"mesh":1,"rotation":[1,0,0,0],"scale":[1,0,0],"translation":[1,0,0]}`), false},
+		{"default", &Node{Matrix: DefaultMatrix}, DefaultMatrix},
+		{"zeros", &Node{Matrix: emptyMatrix}, DefaultMatrix},
+		{"other", &Node{Matrix: [16]float64{2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2}}, [16]float64{2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.n.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Node.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Node.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.n.MatrixOrDefault(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Node.MatrixOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestSkin_MarshalJSON(t *testing.T) {
+func TestNode_RotationOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		s       *Skin
-		want    []byte
-		wantErr bool
+		name string
+		n    *Node
+		want [4]float64
 	}{
-		{"default", &Skin{InverseBindMatrices: -1, Skeleton: -1}, []byte(`{"joints":null}`), false},
-		{"empty", &Skin{InverseBindMatrices: 0, Skeleton: 0}, []byte(`{"inverseBindMatrices":0,"skeleton":0,"joints":null}`), false},
-		{"nodefault", &Skin{InverseBindMatrices: 1, Skeleton: 2}, []byte(`{"inverseBindMatrices":1,"skeleton":2,"joints":null}`), false},
+		{"default", &Node{Rotation: DefaultRotation}, DefaultRotation},
+		{"zeros", &Node{Rotation: emptyRotation}, DefaultRotation},
+		{"other", &Node{Rotation: [4]float64{1, 2, 3, 4}}, [4]float64{1, 2, 3, 4}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.s.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Skin.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Skin.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.n.RotationOrDefault(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Node.RotationOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestPrimitive_MarshalJSON(t *testing.T) {
+func TestNode_ScaleOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		p       *Primitive
-		want    []byte
-		wantErr bool
+		name string
+		n    *Node
+		want [3]float64
 	}{
-		{"default", &Primitive{Indices: -1, Material: -1, Mode: Triangles}, []byte(`{"attributes":null,"mode":4}`), false},
-		{"empty", &Primitive{Indices: 0, Material: 0, Mode: Points}, []byte(`{"attributes":null,"indices":0,"material":0,"mode":0}`), false},
-		{"nodefault", &Primitive{Indices: 1, Material: 2, Mode: TriangleStrip}, []byte(`{"attributes":null,"indices":1,"material":2,"mode":5}`), false},
+		{"default", &Node{Scale: DefaultScale}, DefaultScale},
+		{"zeros", &Node{Scale: emptyScale}, DefaultScale},
+		{"other", &Node{Scale: [3]float64{1, 2, 3}}, [3]float64{1, 2, 3}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.p.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Primitive.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Primitive.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.n.ScaleOrDefault(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Node.ScaleOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestMaterial_MarshalJSON(t *testing.T) {
+func TestNode_TranslationOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		m       *Material
-		want    []byte
-		wantErr bool
+		name string
+		n    *Node
+		want [3]float64
 	}{
-		{"default", &Material{AlphaCutoff: 0.5, AlphaMode: Opaque}, []byte(`{}`), false},
-		{"empty", &Material{AlphaCutoff: 0, AlphaMode: Blend}, []byte(`{"alphaMode":"BLEND","alphaCutoff":0}`), false},
-		{"nodefault", &Material{AlphaCutoff: 1, AlphaMode: Blend}, []byte(`{"alphaMode":"BLEND","alphaCutoff":1}`), false},
+		{"default", &Node{Translation: DefaultTranslation}, DefaultTranslation},
+		{"other", &Node{Translation: [3]float64{1, 2, 3}}, [3]float64{1, 2, 3}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.m.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Material.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Material.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.n.TranslationOrDefault(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Node.TranslationOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestNormalTexture_MarshalJSON(t *testing.T) {
+func TestPBRMetallicRoughness_BaseColorFactorOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		n       *NormalTexture
-		want    []byte
-		wantErr bool
+		name string
+		p    *PBRMetallicRoughness
+		want RGBA
 	}{
-		{"default", &NormalTexture{Index: -1, Scale: -1}, []byte(`{}`), false},
-		{"empty", &NormalTexture{Index: 0, Scale: 0}, []byte(`{"index":0,"scale":0}`), false},
-		{"nodefault", &NormalTexture{Index: 1, Scale: 1}, []byte(`{"index":1,"scale":1}`), false},
+		{"empty", &PBRMetallicRoughness{}, *NewRGBA()},
+		{"other", &PBRMetallicRoughness{BaseColorFactor: &RGBA{0.8, 0.8, 0.8, 0.5}}, RGBA{0.8, 0.8, 0.8, 0.5}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.n.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("NormalTexture.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("NormalTexture.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.p.BaseColorFactorOrDefault(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PBRMetallicRoughness.BaseColorFactorOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestOcclusionTexture_MarshalJSON(t *testing.T) {
+func TestOcclusionTexture_StrengthOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		o       *OcclusionTexture
-		want    []byte
-		wantErr bool
+		name string
+		o    *OcclusionTexture
+		want float64
 	}{
-		{"default", &OcclusionTexture{Index: -1, Strength: 1}, []byte(`{}`), false},
-		{"empty", &OcclusionTexture{Index: 0, Strength: 0}, []byte(`{"index":0,"strength":0}`), false},
-		{"nodefault", &OcclusionTexture{Index: 1, Strength: 0.5}, []byte(`{"index":1,"strength":0.5}`), false},
+		{"empty", &OcclusionTexture{}, 1},
+		{"other", &OcclusionTexture{Strength: Float64(2)}, 2},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.o.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("OcclusionTexture.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("OcclusionTexture.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.o.StrengthOrDefault(); got != tt.want {
+				t.Errorf("OcclusionTexture.StrengthOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestPBRMetallicRoughness_MarshalJSON(t *testing.T) {
+func TestNormalTexture_ScaleOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		p       *PBRMetallicRoughness
-		want    []byte
-		wantErr bool
+		name string
+		n    *NormalTexture
+		want float64
 	}{
-		{"default", &PBRMetallicRoughness{MetallicFactor: 1, RoughnessFactor: 1, BaseColorFactor: [4]float64{1, 1, 1, 1}}, []byte(`{}`), false},
-		{"empty", &PBRMetallicRoughness{MetallicFactor: 0, RoughnessFactor: 0, BaseColorFactor: [4]float64{0, 0, 0, 0}}, []byte(`{"baseColorFactor":[0,0,0,0],"metallicFactor":0,"roughnessFactor":0}`), false},
-		{"nodefault", &PBRMetallicRoughness{MetallicFactor: 0.5, RoughnessFactor: 0.5, BaseColorFactor: [4]float64{1, 0.5, 1, 1}}, []byte(`{"baseColorFactor":[1,0.5,1,1],"metallicFactor":0.5,"roughnessFactor":0.5}`), false},
+		{"empty", &NormalTexture{}, 1},
+		{"other", &NormalTexture{Scale: Float64(2)}, 2},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.p.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("PBRMetallicRoughness.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("PBRMetallicRoughness.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.n.ScaleOrDefault(); got != tt.want {
+				t.Errorf("NormalTexture.ScaleOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestTextureInfo_MarshalJSON(t *testing.T) {
+func TestMaterial_AlphaCutoffOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		t       *TextureInfo
-		want    []byte
-		wantErr bool
+		name string
+		m    *Material
+		want float64
 	}{
-		{"default", &TextureInfo{Index: -1}, []byte(`{}`), false},
-		{"empty", &TextureInfo{Index: 0}, []byte(`{"index":0}`), false},
-		{"nodefault", &TextureInfo{Index: 1}, []byte(`{"index":1}`), false},
+		{"empty", &Material{}, 0.5},
+		{"other", &Material{AlphaCutoff: Float64(2)}, 2},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.t.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("TextureInfo.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("TextureInfo.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.m.AlphaCutoffOrDefault(); got != tt.want {
+				t.Errorf("Material.AlphaCutoffOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestChannel_MarshalJSON(t *testing.T) {
+func TestPBRMetallicRoughness_MetallicFactorOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		ch      *Channel
-		want    []byte
-		wantErr bool
+		name string
+		p    *PBRMetallicRoughness
+		want float64
 	}{
-		{"default", &Channel{Sampler: -1, Target: ChannelTarget{Node: -1}}, []byte(`{"target":{"path":""}}`), false},
-		{"empty", &Channel{Sampler: 0, Target: ChannelTarget{Node: 0}}, []byte(`{"sampler":0,"target":{"node":0,"path":""}}`), false},
-		{"nodefault", &Channel{Sampler: 1, Target: ChannelTarget{Node: 1}}, []byte(`{"sampler":1,"target":{"node":1,"path":""}}`), false},
+		{"empty", &PBRMetallicRoughness{}, 1},
+		{"other", &PBRMetallicRoughness{MetallicFactor: Float64(2)}, 2},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.ch.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Channel.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Channel.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.p.MetallicFactorOrDefault(); got != tt.want {
+				t.Errorf("PBRMetallicRoughness.MetallicFactorOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestTexture_MarshalJSON(t *testing.T) {
+func TestPBRMetallicRoughness_RoughnessFactorOrDefault(t *testing.T) {
 	tests := []struct {
-		name    string
-		t       *Texture
-		want    []byte
-		wantErr bool
+		name string
+		p    *PBRMetallicRoughness
+		want float64
 	}{
-		{"default", &Texture{Sampler: -1, Source: -1}, []byte(`{}`), false},
-		{"empty", &Texture{Sampler: 0, Source: 0}, []byte(`{"sampler":0,"source":0}`), false},
-		{"nodefault", &Texture{Sampler: 1, Source: 1}, []byte(`{"sampler":1,"source":1}`), false},
+		{"empty", &PBRMetallicRoughness{}, 1},
+		{"other", &PBRMetallicRoughness{RoughnessFactor: Float64(2)}, 2},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.t.MarshalJSON()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Texture.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Texture.MarshalJSON() = %v, want %v", string(got), string(tt.want))
+			if got := tt.p.RoughnessFactorOrDefault(); got != tt.want {
+				t.Errorf("PBRMetallicRoughness.RoughnessFactorOrDefault() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func TestBuffer_marshalData(t *testing.T) {
+type fakeExt struct {
+	A int `json:"a"`
+}
+
+func (f *fakeExt) UnmarshalJSON(data []byte) error {
+	type alias fakeExt
+	tmp := alias(fakeExt{})
+	err := json.Unmarshal(data, &tmp)
+	if err == nil {
+		*f = fakeExt(tmp)
+	}
+	return err
+}
+
+func TestExtensions_UnmarshalJSON(t *testing.T) {
+	RegisterExtension("fake_ext", func() json.Unmarshaler { return new(fakeExt) })
+	type args struct {
+		data []byte
+	}
 	tests := []struct {
 		name    string
-		b       *Buffer
-		want    []uint8
+		args    args
+		want    *Extensions
 		wantErr bool
 	}{
-		{"error", &Buffer{URI: "data:application/octet-stream;base64,_"}, []uint8{}, true},
-		{"external", &Buffer{URI: "http://web.com"}, []uint8{}, false},
-		{"empty", &Buffer{URI: "data:application/octet-stream;base64,"}, []uint8{}, false},
-		{"test", &Buffer{URI: "data:application/octet-stream;base64,TEST"}, []uint8{76, 68, 147}, false},
-		{"complex", &Buffer{URI: "data:application/octet-stream;base64,YW55IGNhcm5hbCBwbGVhcw=="}, []uint8{97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115}, false},
+		{"fake_ext", args{[]byte(`{"fake_ext": {"a":2}}`)}, &Extensions{
+			"fake_ext": &fakeExt{A: 2},
+		}, false},
+		{"err", args{[]byte(`{"fake_ext": {{"a":2}}`)}, &Extensions{}, true},
+		{"errext", args{[]byte(`{"fake_ext": {"a":"incorrect"}}`)}, &Extensions{
+			"fake_ext": json.RawMessage([]byte(`{"a":"incorrect"}`)),
+		}, false},
+		{"noregistered", args{[]byte(`{"fake_ext_1": {"a":"incorrect"}}`)}, &Extensions{
+			"fake_ext_1": json.RawMessage([]byte(`{"a":"incorrect"}`)),
+		}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.b.marshalData()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Buffer.marshalData() error = %v, wantErr %v", err, tt.wantErr)
+			ext := Extensions{}
+			if err := ext.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
+				t.Errorf("Extensions.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Buffer.marshalData() = %v, want %v", got, tt.want)
+			if !reflect.DeepEqual(&ext, tt.want) {
+				t.Errorf("PBRSpecularGlossiness.MarshalJSON() = %v, want %v", &ext, tt.want)
 			}
 		})
 	}
diff --git a/validator_test.go b/validator_test.go
index 335bfca..a8ad5a9 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -14,20 +14,20 @@ func TestValidateDocument(t *testing.T) {
 	}{
 		{"Document.Asset.Version", new(Document), true},
 		{"Document.Accessors[0].ComponentType", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: 1, Count: 1, Type: "SCALAR"}}}, true},
+			Accessors: []Accessor{{ComponentType: 10, Count: 1}}}, true},
 		{"Document.Accessors[0].Count", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: Byte, Count: 0, Type: "SCALAR"}}}, true},
+			Accessors: []Accessor{{ComponentType: Byte, Count: 0}}}, true},
 		{"Document.Accessors[0].Type", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Type: "OTHER"}}}, true},
+			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Type: 10}}}, true},
 		{"Document.Accessors[0].Max", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Type: "SCALAR", Max: make([]float64, 17)}}}, true},
+			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Max: make([]float64, 17)}}}, true},
 		{"Document.Accessors[0].Min", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Type: "SCALAR", Min: make([]float64, 17)}}}, true},
+			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Min: make([]float64, 17)}}}, true},
 		{"Document.Accessors[0].Sparse.Count", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Type: "SCALAR",
+			Accessors: []Accessor{{ComponentType: Byte, Count: 1,
 				Sparse: &Sparse{Count: 0}}}}, true},
 		{"Document.Accessors[0].Sparse.Indices.ComponentType", &Document{Asset: Asset{Version: "1.0"},
-			Accessors: []Accessor{{ComponentType: Byte, Count: 1, Type: "SCALAR",
+			Accessors: []Accessor{{ComponentType: Byte, Count: 1,
 				Sparse: &Sparse{Count: 1, Indices: SparseIndices{ComponentType: 1}}}}}, true},
 		{"Document.Buffers[0].URI", &Document{Asset: Asset{Version: "1.0"},
 			Buffers: []Buffer{{ByteLength: 1, URI: "a.bin"}}}, false},
@@ -51,12 +51,10 @@ func TestValidateDocument(t *testing.T) {
 			Nodes: []Node{{Rotation: [4]float64{1, -2, 1, 1}}}}, true},
 		{"Document.Skins[0].Joints", &Document{Asset: Asset{Version: "1.0"},
 			Skins: []Skin{{Joints: []uint32{1, 1}}}}, true},
-		{"Document.Cameras[0].Type", &Document{Asset: Asset{Version: "1.0"},
-			Cameras: []Camera{{Type: "incorrect"}}}, true},
 		{"Document.Cameras[0].Orthographic.Znear", &Document{Asset: Asset{Version: "1.0"},
-			Cameras: []Camera{{Type: OrthographicType, Orthographic: &Orthographic{Znear: -1, Zfar: 1}}}}, true},
+			Cameras: []Camera{{Orthographic: &Orthographic{Znear: -1, Zfar: 1}}}}, true},
 		{"Document.Cameras[0].Orthographic.Zfar", &Document{Asset: Asset{Version: "1.0"},
-			Cameras: []Camera{{Type: OrthographicType, Orthographic: &Orthographic{Znear: 1, Zfar: 1}}}}, true},
+			Cameras: []Camera{{Orthographic: &Orthographic{Znear: 1, Zfar: 1}}}}, true},
 		{"Document.Meshes[0].Primitives", &Document{Asset: Asset{Version: "1.0"},
 			Meshes: []Mesh{{Primitives: make([]Primitive, 0)}}}, true},
 		{"Document.Meshes[0].Primitives[0].Mode", &Document{Asset: Asset{Version: "1.0"},
@@ -68,33 +66,33 @@ func TestValidateDocument(t *testing.T) {
 		{"Document.Materials[0].EmissiveFactor[1]", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque, EmissiveFactor: [3]float64{1, 2, 1}}}}, true},
 		{"Document.Materials[0].AlphaMode", &Document{Asset: Asset{Version: "1.0"},
-			Materials: []Material{{AlphaMode: "OTHER"}}}, true},
-		{"Document.Materials[0].PBRMetallicRoughness.BaseColorFactor[0]", &Document{Asset: Asset{Version: "1.0"},
+			Materials: []Material{{AlphaMode: 5}}}, true},
+		{"Document.Materials[0].PBRMetallicRoughness.BaseColorFactor.R", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque,
-				PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: [4]float64{-1, 0, 0, 0}}}}}, true},
-		{"Document.Materials[0].PBRMetallicRoughness.BaseColorFactor[1]", &Document{Asset: Asset{Version: "1.0"},
+				PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &RGBA{R: -0.1, G: 0.8, B: 0.8, A: 1}}}}}, true},
+		{"Document.Materials[0].PBRMetallicRoughness.BaseColorFactor.G", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque,
-				PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: [4]float64{1, 2, 0, 0}}}}}, true},
+				PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &RGBA{R: 1, G: 1.1, B: 0, A: 0}}}}}, true},
 		{"Document.Materials[0].PBRMetallicRoughness.MetallicFactor", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque,
-				PBRMetallicRoughness: &PBRMetallicRoughness{MetallicFactor: 2}}}}, true},
+				PBRMetallicRoughness: &PBRMetallicRoughness{MetallicFactor: Float64(2)}}}}, true},
 		{"Document.Materials[0].PBRMetallicRoughness.MetallicFactor", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque,
-				PBRMetallicRoughness: &PBRMetallicRoughness{MetallicFactor: -1}}}}, true},
+				PBRMetallicRoughness: &PBRMetallicRoughness{MetallicFactor: Float64(-1)}}}}, true},
 		{"Document.Materials[0].PBRMetallicRoughness.RoughnessFactor", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque,
-				PBRMetallicRoughness: &PBRMetallicRoughness{RoughnessFactor: 2}}}}, true},
+				PBRMetallicRoughness: &PBRMetallicRoughness{RoughnessFactor: Float64(2)}}}}, true},
 		{"Document.Materials[0].PBRMetallicRoughness.RoughnessFactor", &Document{Asset: Asset{Version: "1.0"},
 			Materials: []Material{{AlphaMode: Opaque,
-				PBRMetallicRoughness: &PBRMetallicRoughness{RoughnessFactor: -1}}}}, true},
+				PBRMetallicRoughness: &PBRMetallicRoughness{RoughnessFactor: Float64(-1)}}}}, true},
 		{"Document.Samplers[0].MagFilter", &Document{Asset: Asset{Version: "1.0"},
-			Samplers: []Sampler{{MagFilter: 1, MinFilter: MinLinear, WrapS: ClampToEdge, WrapT: ClampToEdge}}}, true},
+			Samplers: []Sampler{{MagFilter: 10, MinFilter: MinLinear, WrapS: ClampToEdge, WrapT: ClampToEdge}}}, true},
 		{"Document.Samplers[0].MinFilter", &Document{Asset: Asset{Version: "1.0"},
-			Samplers: []Sampler{{MagFilter: MagLinear, MinFilter: 1, WrapS: ClampToEdge, WrapT: ClampToEdge}}}, true},
+			Samplers: []Sampler{{MagFilter: MagLinear, MinFilter: 10, WrapS: ClampToEdge, WrapT: ClampToEdge}}}, true},
 		{"Document.Samplers[0].WrapS", &Document{Asset: Asset{Version: "1.0"},
-			Samplers: []Sampler{{MagFilter: MagLinear, MinFilter: MinLinear, WrapS: 1, WrapT: ClampToEdge}}}, true},
+			Samplers: []Sampler{{MagFilter: MagLinear, MinFilter: MinLinear, WrapS: 10, WrapT: ClampToEdge}}}, true},
 		{"Document.Samplers[0].WrapT", &Document{Asset: Asset{Version: "1.0"},
-			Samplers: []Sampler{{MagFilter: MagLinear, MinFilter: MinLinear, WrapS: ClampToEdge, WrapT: 1}}}, true},
+			Samplers: []Sampler{{MagFilter: MagLinear, MinFilter: MinLinear, WrapS: ClampToEdge, WrapT: 10}}}, true},
 		{"Document.Images[0].URI", &Document{Asset: Asset{Version: "1.0"},
 			Images: []Image{{URI: "a.png"}}}, false},
 		{"Document.Images[0].MimeType", &Document{Asset: Asset{Version: "1.0"},
@@ -102,10 +100,10 @@ func TestValidateDocument(t *testing.T) {
 		{"Document.Animations[0].Channels", &Document{Asset: Asset{Version: "1.0"},
 			Animations: []Animation{{Samplers: []AnimationSampler{{}}}}}, true},
 		{"Document.Animations[0].Channels[0].Target.Path", &Document{Asset: Asset{Version: "1.0"},
-			Animations: []Animation{{Channels: []Channel{{Target: ChannelTarget{Path: "other"}}}, Samplers: []AnimationSampler{{}}}}}, true},
+			Animations: []Animation{{Channels: []Channel{{Target: ChannelTarget{Path: 10}}}, Samplers: []AnimationSampler{{}}}}}, true},
 		{"Document.Animations[0].Samplers[0].Interpolation", &Document{Asset: Asset{Version: "1.0"},
-			Animations: []Animation{{Channels: []Channel{{Target: ChannelTarget{Path: "translation"}}},
-				Samplers: []AnimationSampler{{Interpolation: "OTHER"}}}}}, true},
+			Animations: []Animation{{Channels: []Channel{{Target: ChannelTarget{Path: Translation}}},
+				Samplers: []AnimationSampler{{Interpolation: 10}}}}}, true},
 		{"ok", &Document{
 			ExtensionsUsed:     []string{"one", "another"},
 			ExtensionsRequired: []string{"that", "this"},