Repository: jensneuse/graphql-go-tools
Pull request #218 (2020-05-20 17:31:30)

Update with tyk master


Patch:
diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go
index af1fdcf2..f57caf2f 100644
--- a/pkg/ast/ast.go
+++ b/pkg/ast/ast.go
@@ -174,6 +174,18 @@ func (d *Document) NextRefIndex() int {
 	return d.RefIndex
 }
 
+func (d *Document) AddRootNode(node Node) {
+	d.RootNodes = append(d.RootNodes, node)
+	d.Index.Add(d.NodeNameString(node), node)
+}
+
+func (d *Document) ImportRootNode(ref int, kind NodeKind) {
+	d.AddRootNode(Node{
+		Kind: kind,
+		Ref:  ref,
+	})
+}
+
 func (d *Document) DeleteRootNodes(nodes []Node) {
 	for i := range nodes {
 		d.DeleteRootNode(nodes[i])
@@ -221,21 +233,13 @@ func (d *Document) NodeByName(name ByteSlice) (Node, bool) {
 	return node, exists
 }
 
-func (d *Document) HasSchemaDefinition() bool {
-	for i := range d.RootNodes {
-		if d.RootNodes[i].Kind == NodeKindSchemaDefinition {
-			return true
-		}
+func (d *Document) TypeDefinitionContainsImplementsInterface(typeName, interfaceName ByteSlice) bool {
+	typeDefinition, exists := d.Index.Nodes[xxhash.Sum64(typeName)]
+	if !exists {
+		return false
 	}
-
-	return false
-}
-
-func (d *Document) AddSchemaDefinitionRootNode(schemaDefinition SchemaDefinition) {
-	ref := d.AddSchemaDefinition(schemaDefinition)
-	schemaNode := Node{
-		Kind: NodeKindSchemaDefinition,
-		Ref:  ref,
+	if typeDefinition.Kind != NodeKindObjectTypeDefinition {
+		return false
 	}
-	d.RootNodes = append([]Node{schemaNode}, d.RootNodes...)
+	return d.ObjectTypeDefinitionImplementsInterface(typeDefinition.Ref, interfaceName)
 }
diff --git a/pkg/ast/ast_argument.go b/pkg/ast/ast_argument.go
index 6696bb94..eef452aa 100644
--- a/pkg/ast/ast_argument.go
+++ b/pkg/ast/ast_argument.go
@@ -127,3 +127,24 @@ func (d *Document) ArgumentsAfter(ancestor Node, argument int) []int {
 	}
 	return nil
 }
+
+func (d *Document) AddArgument(argument Argument) (ref int) {
+	d.Arguments = append(d.Arguments, argument)
+	return len(d.Arguments) - 1
+}
+
+func (d *Document) ImportVariableValueArgument(argName, variableName ByteSlice) (variableValueRef, argRef int) {
+	variableValueRef = d.ImportVariableValue(variableName)
+
+	arg := Argument{
+		Name: d.Input.AppendInputBytes(argName),
+		Value: Value{
+			Kind: ValueKindVariable,
+			Ref:  variableValueRef,
+		},
+	}
+
+	argRef = d.AddArgument(arg)
+
+	return
+}
diff --git a/pkg/ast/ast_description.go b/pkg/ast/ast_description.go
index 6bb4c18b..18611d8b 100644
--- a/pkg/ast/ast_description.go
+++ b/pkg/ast/ast_description.go
@@ -2,6 +2,7 @@ package ast
 
 import (
 	"io"
+	"strings"
 
 	"github.com/jensneuse/graphql-go-tools/pkg/lexer/literal"
 	"github.com/jensneuse/graphql-go-tools/pkg/lexer/position"
@@ -65,3 +66,15 @@ func (d *Document) PrintDescription(description Description, indent []byte, dept
 	}
 	return nil
 }
+
+func (d *Document) ImportDescription(desc string) (description Description) {
+	if desc == "" {
+		return
+	}
+
+	return Description{
+		IsDefined:     true,
+		IsBlockString: strings.Contains(desc, "\n"),
+		Content:       d.Input.AppendInputString(desc),
+	}
+}
diff --git a/pkg/ast/ast_directive_definition.go b/pkg/ast/ast_directive_definition.go
index f54340d9..388c6c1a 100644
--- a/pkg/ast/ast_directive_definition.go
+++ b/pkg/ast/ast_directive_definition.go
@@ -100,3 +100,30 @@ func (d *Document) DirectiveDefinitionArgumentDefaultValueFloat32(directiveName,
 	}
 	return d.FloatValueAsFloat32(defaultValue.Ref)
 }
+
+func (d *Document) AddDirectiveDefinition(directiveDefinition DirectiveDefinition) (ref int) {
+	d.DirectiveDefinitions = append(d.DirectiveDefinitions, directiveDefinition)
+	return len(d.DirectiveDefinitions) - 1
+}
+
+func (d *Document) ImportDirectiveDefinition(name, description string, argsRefs []int, locations []string) (ref int) {
+	directiveLocations := DirectiveLocations{}
+	for _, location := range locations {
+		_ = directiveLocations.SetFromRaw([]byte(location))
+	}
+
+	definition := DirectiveDefinition{
+		Description:             d.ImportDescription(description),
+		Name:                    d.Input.AppendInputString(name),
+		HasArgumentsDefinitions: len(argsRefs) > 0,
+		ArgumentsDefinition: InputValueDefinitionList{
+			Refs: argsRefs,
+		},
+		DirectiveLocations: directiveLocations,
+	}
+
+	ref = d.AddDirectiveDefinition(definition)
+	d.ImportRootNode(ref, NodeKindDirectiveDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_enum_type_definition.go b/pkg/ast/ast_enum_type_definition.go
index f82f11ae..7a2ab5cd 100644
--- a/pkg/ast/ast_enum_type_definition.go
+++ b/pkg/ast/ast_enum_type_definition.go
@@ -60,3 +60,24 @@ func (d *Document) EnumTypeDefinitionContainsEnumValue(enumTypeDef int, valueNam
 	}
 	return false
 }
+
+func (d *Document) AddEnumTypeDefinition(definition EnumTypeDefinition) (ref int) {
+	d.EnumTypeDefinitions = append(d.EnumTypeDefinitions, definition)
+	return len(d.EnumTypeDefinitions) - 1
+}
+
+func (d *Document) ImportEnumTypeDefinition(name, description string, valueRefs []int) (ref int) {
+	definition := EnumTypeDefinition{
+		Description:             d.ImportDescription(description),
+		Name:                    d.Input.AppendInputString(name),
+		HasEnumValuesDefinition: len(valueRefs) > 0,
+		EnumValuesDefinition: EnumValueDefinitionList{
+			Refs: valueRefs,
+		},
+	}
+
+	ref = d.AddEnumTypeDefinition(definition)
+	d.ImportRootNode(ref, NodeKindEnumTypeDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_enum_value_definition.go b/pkg/ast/ast_enum_value_definition.go
index 9a9c239f..f1ba8c8a 100644
--- a/pkg/ast/ast_enum_value_definition.go
+++ b/pkg/ast/ast_enum_value_definition.go
@@ -7,7 +7,7 @@ import (
 
 type EnumValueDefinitionList struct {
 	LBRACE position.Position // {
-	Refs   []int             //
+	Refs   []int             // EnumValueDefinition
 	RBRACE position.Position // }
 }
 
@@ -65,3 +65,17 @@ func (d *Document) EnumValueDefinitionIsLast(ref int, ancestor Node) bool {
 		return false
 	}
 }
+
+func (d *Document) AddEnumValueDefinition(inputValueDefinition EnumValueDefinition) (ref int) {
+	d.EnumValueDefinitions = append(d.EnumValueDefinitions, inputValueDefinition)
+	return len(d.EnumValueDefinitions) - 1
+}
+
+func (d *Document) ImportEnumValueDefinition(value, description string) (ref int) {
+	inputValueDef := EnumValueDefinition{
+		Description: d.ImportDescription(description),
+		EnumValue:   d.Input.AppendInputString(value),
+	}
+
+	return d.AddEnumValueDefinition(inputValueDef)
+}
diff --git a/pkg/ast/ast_field_definition.go b/pkg/ast/ast_field_definition.go
index 8044ac21..19267a94 100644
--- a/pkg/ast/ast_field_definition.go
+++ b/pkg/ast/ast_field_definition.go
@@ -90,6 +90,20 @@ func (d *Document) AddFieldDefinition(fieldDefinition FieldDefinition) (ref int)
 	return len(d.FieldDefinitions) - 1
 }
 
+func (d *Document) ImportFieldDefinition(name, description string, typeRef int, argsRefs []int) (ref int) {
+	fieldDef := FieldDefinition{
+		Name:        d.Input.AppendInputString(name),
+		Type:        typeRef,
+		Description: d.ImportDescription(description),
+		ArgumentsDefinition: InputValueDefinitionList{
+			Refs: argsRefs,
+		},
+		HasArgumentsDefinitions: len(argsRefs) > 0,
+	}
+
+	return d.AddFieldDefinition(fieldDef)
+}
+
 func (d *Document) FieldDefinitionsContainField(definitions []int, field ByteSlice) bool {
 	for _, i := range definitions {
 		if bytes.Equal(field, d.FieldDefinitionNameBytes(i)) {
diff --git a/pkg/ast/ast_input_object_type_definition.go b/pkg/ast/ast_input_object_type_definition.go
index c78d3d7c..c4aadb2e 100644
--- a/pkg/ast/ast_input_object_type_definition.go
+++ b/pkg/ast/ast_input_object_type_definition.go
@@ -35,7 +35,7 @@ func (d *Document) InputObjectTypeDefinitionDescriptionBytes(ref int) ByteSlice
 }
 
 func (d *Document) InputObjectTypeDefinitionDescriptionString(ref int) string {
-	return unsafebytes.BytesToString(d.InputObjectTypeDefinitionNameBytes(ref))
+	return unsafebytes.BytesToString(d.InputObjectTypeDefinitionDescriptionBytes(ref))
 }
 
 func (d *Document) InputObjectTypeDefinitionInputValueDefinitionDefaultValueString(inputObjectTypeDefinitionName, inputValueDefinitionName string) string {
@@ -90,3 +90,24 @@ func (d *Document) InputObjectTypeDefinitionInputValueDefinitionByName(definitio
 	}
 	return -1
 }
+
+func (d *Document) AddInputObjectTypeDefinition(definition InputObjectTypeDefinition) (ref int) {
+	d.InputObjectTypeDefinitions = append(d.InputObjectTypeDefinitions, definition)
+	return len(d.InputObjectTypeDefinitions) - 1
+}
+
+func (d *Document) ImportInputObjectTypeDefinition(name, description string, argsRefs []int) (ref int) {
+	definition := InputObjectTypeDefinition{
+		Description:              d.ImportDescription(description),
+		Name:                     d.Input.AppendInputString(name),
+		HasInputFieldsDefinition: len(argsRefs) > 0,
+		InputFieldsDefinition: InputValueDefinitionList{
+			Refs: argsRefs,
+		},
+	}
+
+	ref = d.AddInputObjectTypeDefinition(definition)
+	d.ImportRootNode(ref, NodeKindInputObjectTypeDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_input_value_definition.go b/pkg/ast/ast_input_value_definition.go
index ce01f659..6b8d8f67 100644
--- a/pkg/ast/ast_input_value_definition.go
+++ b/pkg/ast/ast_input_value_definition.go
@@ -82,3 +82,14 @@ func (d *Document) AddInputValueDefinition(inputValueDefinition InputValueDefini
 	d.InputValueDefinitions = append(d.InputValueDefinitions, inputValueDefinition)
 	return len(d.InputValueDefinitions) - 1
 }
+
+func (d *Document) ImportInputValueDefinition(name, description string, typeRef int, defaultValue DefaultValue) (ref int) {
+	inputValueDef := InputValueDefinition{
+		Description:  d.ImportDescription(description),
+		Name:         d.Input.AppendInputString(name),
+		Type:         typeRef,
+		DefaultValue: defaultValue,
+	}
+
+	return d.AddInputValueDefinition(inputValueDef)
+}
diff --git a/pkg/ast/ast_interface_type_definition.go b/pkg/ast/ast_interface_type_definition.go
index e40523ad..69413c03 100644
--- a/pkg/ast/ast_interface_type_definition.go
+++ b/pkg/ast/ast_interface_type_definition.go
@@ -38,3 +38,24 @@ func (d *Document) InterfaceTypeDefinitionDescriptionBytes(ref int) ByteSlice {
 func (d *Document) InterfaceTypeDefinitionDescriptionString(ref int) string {
 	return unsafebytes.BytesToString(d.InterfaceTypeDefinitionDescriptionBytes(ref))
 }
+
+func (d *Document) AddInterfaceTypeDefinition(definition InterfaceTypeDefinition) (ref int) {
+	d.InterfaceTypeDefinitions = append(d.InterfaceTypeDefinitions, definition)
+	return len(d.InterfaceTypeDefinitions) - 1
+}
+
+func (d *Document) ImportInterfaceTypeDefinition(name, description string, fieldRefs []int) (ref int) {
+	definition := InterfaceTypeDefinition{
+		Name:        d.Input.AppendInputString(name),
+		Description: d.ImportDescription(description),
+		FieldsDefinition: FieldDefinitionList{
+			Refs: fieldRefs,
+		},
+		HasFieldDefinitions: len(fieldRefs) > 0,
+	}
+
+	ref = d.AddInterfaceTypeDefinition(definition)
+	d.ImportRootNode(ref, NodeKindInterfaceTypeDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_object_field.go b/pkg/ast/ast_object_field.go
index 35fe1225..585de784 100644
--- a/pkg/ast/ast_object_field.go
+++ b/pkg/ast/ast_object_field.go
@@ -45,3 +45,15 @@ func (d *Document) ObjectValuesAreEqual(left, right int) bool {
 	}
 	return true
 }
+
+func (d *Document) AddObjectField(field ObjectField) (ref int) {
+	d.ObjectFields = append(d.ObjectFields, field)
+	return len(d.ObjectFields) - 1
+}
+
+func (d *Document) ImportObjectField(name ByteSlice, value Value) (ref int) {
+	return d.AddObjectField(ObjectField{
+		Name:  d.Input.AppendInputBytes(name),
+		Value: value,
+	})
+}
diff --git a/pkg/ast/ast_object_type_definition.go b/pkg/ast/ast_object_type_definition.go
index b3400bc4..59b50dde 100644
--- a/pkg/ast/ast_object_type_definition.go
+++ b/pkg/ast/ast_object_type_definition.go
@@ -3,8 +3,6 @@ package ast
 import (
 	"bytes"
 
-	"github.com/cespare/xxhash"
-
 	"github.com/jensneuse/graphql-go-tools/internal/pkg/unsafebytes"
 	"github.com/jensneuse/graphql-go-tools/pkg/lexer/position"
 )
@@ -57,20 +55,36 @@ func (d *Document) ObjectTypeDefinitionHasField(ref int, fieldName []byte) bool
 	return false
 }
 
-// TODO: to be consistent consider renaming to ObjectTypeDefinitionContainsImplementsInterface
-func (d *Document) TypeDefinitionContainsImplementsInterface(typeName, interfaceName ByteSlice) bool {
-	typeDefinition, exists := d.Index.Nodes[xxhash.Sum64(typeName)]
-	if !exists {
-		return false
-	}
-	if typeDefinition.Kind != NodeKindObjectTypeDefinition {
-		return false
-	}
-	for _, i := range d.ObjectTypeDefinitions[typeDefinition.Ref].ImplementsInterfaces.Refs {
-		implements := d.ResolveTypeNameBytes(i)
+func (d *Document) ObjectTypeDefinitionImplementsInterface(definitionRef int, interfaceName ByteSlice) bool {
+	for _, iRef := range d.ObjectTypeDefinitions[definitionRef].ImplementsInterfaces.Refs {
+		implements := d.ResolveTypeNameBytes(iRef)
 		if bytes.Equal(interfaceName, implements) {
 			return true
 		}
 	}
 	return false
 }
+
+func (d *Document) AddObjectTypeDefinition(definition ObjectTypeDefinition) (ref int) {
+	d.ObjectTypeDefinitions = append(d.ObjectTypeDefinitions, definition)
+	return len(d.ObjectTypeDefinitions) - 1
+}
+
+func (d *Document) ImportObjectTypeDefinition(name, description string, fieldRefs []int, iRefs []int) (ref int) {
+	definition := ObjectTypeDefinition{
+		Name:        d.Input.AppendInputString(name),
+		Description: d.ImportDescription(description),
+		FieldsDefinition: FieldDefinitionList{
+			Refs: fieldRefs,
+		},
+		HasFieldDefinitions: len(fieldRefs) > 0,
+		ImplementsInterfaces: TypeList{
+			Refs: iRefs,
+		},
+	}
+
+	ref = d.AddObjectTypeDefinition(definition)
+	d.ImportRootNode(ref, NodeKindObjectTypeDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_root_operation_type_definition.go b/pkg/ast/ast_root_operation_type_definition.go
index e1f4e13e..3769e41e 100644
--- a/pkg/ast/ast_root_operation_type_definition.go
+++ b/pkg/ast/ast_root_operation_type_definition.go
@@ -72,3 +72,14 @@ func (d *Document) AddRootOperationTypeDefinition(rootOperationTypeDefinition Ro
 	d.RootOperationTypeDefinitions = append(d.RootOperationTypeDefinitions, rootOperationTypeDefinition)
 	return len(d.RootOperationTypeDefinitions) - 1
 }
+
+func (d *Document) ImportRootOperationTypeDefinition(name string, operationType OperationType) (ref int) {
+	operationTypeDefinition := RootOperationTypeDefinition{
+		OperationType: operationType,
+		NamedType: Type{
+			Name: d.Input.AppendInputString(name),
+		},
+	}
+
+	return d.AddRootOperationTypeDefinition(operationTypeDefinition)
+}
diff --git a/pkg/ast/ast_scalar_type_definition.go b/pkg/ast/ast_scalar_type_definition.go
index 7847dfa3..c9e8ccea 100644
--- a/pkg/ast/ast_scalar_type_definition.go
+++ b/pkg/ast/ast_scalar_type_definition.go
@@ -24,6 +24,34 @@ func (d *Document) ScalarTypeDefinitionNameString(ref int) string {
 	return unsafebytes.BytesToString(d.Input.ByteSlice(d.ScalarTypeDefinitions[ref].Name))
 }
 
+func (d *Document) ScalarTypeDefinitionDescriptionBytes(ref int) ByteSlice {
+	if !d.ScalarTypeDefinitions[ref].Description.IsDefined {
+		return nil
+	}
+	return d.Input.ByteSlice(d.ScalarTypeDefinitions[ref].Description.Content)
+}
+
+func (d *Document) ScalarTypeDefinitionDescriptionString(ref int) string {
+	return unsafebytes.BytesToString(d.ScalarTypeDefinitionDescriptionBytes(ref))
+}
+
 func (d *Document) ScalarTypeDefinitionHasDirectives(ref int) bool {
 	return d.ScalarTypeDefinitions[ref].HasDirectives
 }
+
+func (d *Document) AddScalarTypeDefinition(definition ScalarTypeDefinition) (ref int) {
+	d.ScalarTypeDefinitions = append(d.ScalarTypeDefinitions, definition)
+	return len(d.ScalarTypeDefinitions) - 1
+}
+
+func (d *Document) ImportScalarTypeDefinition(name, description string) (ref int) {
+	definition := ScalarTypeDefinition{
+		Description: d.ImportDescription(description),
+		Name:        d.Input.AppendInputString(name),
+	}
+
+	ref = d.AddScalarTypeDefinition(definition)
+	d.ImportRootNode(ref, NodeKindScalarTypeDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_schema_definition.go b/pkg/ast/ast_schema_definition.go
index e31a3a79..c1f6054e 100644
--- a/pkg/ast/ast_schema_definition.go
+++ b/pkg/ast/ast_schema_definition.go
@@ -13,7 +13,48 @@ func (s *SchemaDefinition) AddRootOperationTypeDefinitionRefs(refs ...int) {
 	s.RootOperationTypeDefinitions.Refs = append(s.RootOperationTypeDefinitions.Refs, refs...)
 }
 
+func (d *Document) HasSchemaDefinition() bool {
+	for i := range d.RootNodes {
+		if d.RootNodes[i].Kind == NodeKindSchemaDefinition {
+			return true
+		}
+	}
+
+	return false
+}
+
 func (d *Document) AddSchemaDefinition(schemaDefinition SchemaDefinition) (ref int) {
 	d.SchemaDefinitions = append(d.SchemaDefinitions, schemaDefinition)
 	return len(d.SchemaDefinitions) - 1
 }
+
+func (d *Document) AddSchemaDefinitionRootNode(schemaDefinition SchemaDefinition) {
+	ref := d.AddSchemaDefinition(schemaDefinition)
+	schemaNode := Node{
+		Kind: NodeKindSchemaDefinition,
+		Ref:  ref,
+	}
+	d.RootNodes = append([]Node{schemaNode}, d.RootNodes...)
+}
+
+func (d *Document) ImportSchemaDefinition(queryTypeName, mutationTypeName, subscriptionTypeName string) {
+	var operationRefs []int
+
+	if queryTypeName != "" {
+		operationRefs = append(operationRefs, d.ImportRootOperationTypeDefinition(queryTypeName, OperationTypeQuery))
+	}
+	if mutationTypeName != "" {
+		operationRefs = append(operationRefs, d.ImportRootOperationTypeDefinition(mutationTypeName, OperationTypeMutation))
+	}
+	if subscriptionTypeName != "" {
+		operationRefs = append(operationRefs, d.ImportRootOperationTypeDefinition(subscriptionTypeName, OperationTypeSubscription))
+	}
+
+	schemaDefinition := SchemaDefinition{
+		RootOperationTypeDefinitions: RootOperationTypeDefinitionList{
+			Refs: operationRefs,
+		},
+	}
+
+	d.AddSchemaDefinitionRootNode(schemaDefinition)
+}
diff --git a/pkg/ast/ast_type.go b/pkg/ast/ast_type.go
index 95c65064..dffdfbb1 100644
--- a/pkg/ast/ast_type.go
+++ b/pkg/ast/ast_type.go
@@ -73,6 +73,11 @@ func (d *Document) PrintTypeBytes(ref int, buf []byte) ([]byte, error) {
 	return b.Bytes(), err
 }
 
+func (d *Document) AddType(t Type) (ref int) {
+	d.Types = append(d.Types, t)
+	return len(d.Types) - 1
+}
+
 func (d *Document) AddNamedType(name []byte) (ref int) {
 	nameRef := d.Input.AppendInputBytes(name)
 	d.Types = append(d.Types, Type{
diff --git a/pkg/ast/ast_union_type_definition.go b/pkg/ast/ast_union_type_definition.go
index 1c3dd46c..692d73a4 100644
--- a/pkg/ast/ast_union_type_definition.go
+++ b/pkg/ast/ast_union_type_definition.go
@@ -67,3 +67,24 @@ func (d *Document) UnionMemberTypeIsLast(ref int, ancestor Node) bool {
 func (d *Document) UnionTypeDefinitionHasDirectives(ref int) bool {
 	return d.UnionTypeDefinitions[ref].HasDirectives
 }
+
+func (d *Document) AddUnionTypeDefinition(definition UnionTypeDefinition) (ref int) {
+	d.UnionTypeDefinitions = append(d.UnionTypeDefinitions, definition)
+	return len(d.UnionTypeDefinitions) - 1
+}
+
+func (d *Document) ImportUnionTypeDefinition(name, description string, typeRefs []int) (ref int) {
+	definition := UnionTypeDefinition{
+		Name:                d.Input.AppendInputString(name),
+		Description:         d.ImportDescription(description),
+		HasUnionMemberTypes: len(typeRefs) > 0,
+		UnionMemberTypes: TypeList{
+			Refs: typeRefs,
+		},
+	}
+
+	ref = d.AddUnionTypeDefinition(definition)
+	d.ImportRootNode(ref, NodeKindUnionTypeDefinition)
+
+	return
+}
diff --git a/pkg/ast/ast_val_enum_value.go b/pkg/ast/ast_val_enum_value.go
index 2ec6867e..8940a4fa 100644
--- a/pkg/ast/ast_val_enum_value.go
+++ b/pkg/ast/ast_val_enum_value.go
@@ -24,3 +24,14 @@ func (d *Document) EnumValueNameString(ref int) string {
 func (d *Document) EnumValuesAreEqual(left, right int) bool {
 	return d.Input.ByteSliceReferenceContentEquals(d.EnumValueName(left), d.EnumValueName(right))
 }
+
+func (d *Document) AddEnumValue(value EnumValue) (ref int) {
+	d.EnumValues = append(d.EnumValues, value)
+	return len(d.EnumValues) - 1
+}
+
+func (d *Document) ImportEnumValue(name ByteSlice) (ref int) {
+	return d.AddEnumValue(EnumValue{
+		Name: d.Input.AppendInputBytes(name),
+	})
+}
diff --git a/pkg/ast/ast_val_float_value.go b/pkg/ast/ast_val_float_value.go
index ee7aa6df..4c50f3d6 100644
--- a/pkg/ast/ast_val_float_value.go
+++ b/pkg/ast/ast_val_float_value.go
@@ -37,3 +37,15 @@ func (d *Document) FloatValuesAreEqual(left, right int) bool {
 	return d.FloatValueIsNegative(left) == d.FloatValueIsNegative(right) &&
 		bytes.Equal(d.FloatValueRaw(left), d.FloatValueRaw(right))
 }
+
+func (d *Document) AddFloatValue(value FloatValue) (ref int) {
+	d.FloatValues = append(d.FloatValues, value)
+	return len(d.FloatValues) - 1
+}
+
+func (d *Document) ImportFloatValue(raw ByteSlice, isNegative bool) (ref int) {
+	return d.AddFloatValue(FloatValue{
+		Negative: isNegative,
+		Raw:      d.Input.AppendInputBytes(raw),
+	})
+}
diff --git a/pkg/ast/ast_val_int_value.go b/pkg/ast/ast_val_int_value.go
index b7aedb91..6cf9e90b 100644
--- a/pkg/ast/ast_val_int_value.go
+++ b/pkg/ast/ast_val_int_value.go
@@ -41,3 +41,15 @@ func (d *Document) IntValuesAreEquals(left, right int) bool {
 	return d.IntValueIsNegative(left) == d.IntValueIsNegative(right) &&
 		bytes.Equal(d.IntValueRaw(left), d.IntValueRaw(right))
 }
+
+func (d *Document) AddIntValue(value IntValue) (ref int) {
+	d.IntValues = append(d.IntValues, value)
+	return len(d.IntValues) - 1
+}
+
+func (d *Document) ImportIntValue(raw ByteSlice, isNegative bool) (ref int) {
+	return d.AddIntValue(IntValue{
+		Negative: isNegative,
+		Raw:      d.Input.AppendInputBytes(raw),
+	})
+}
diff --git a/pkg/ast/ast_val_list_value.go b/pkg/ast/ast_val_list_value.go
index f2c5511a..4d2aaba0 100644
--- a/pkg/ast/ast_val_list_value.go
+++ b/pkg/ast/ast_val_list_value.go
@@ -4,7 +4,7 @@ import "github.com/jensneuse/graphql-go-tools/pkg/lexer/position"
 
 type ListValue struct {
 	LBRACK position.Position // [
-	Refs   []int             // Path
+	Refs   []int             // Value
 	RBRACK position.Position // ]
 }
 
@@ -22,3 +22,14 @@ func (d *Document) ListValuesAreEqual(left, right int) bool {
 	}
 	return true
 }
+
+func (d *Document) AddListValue(value ListValue) (ref int) {
+	d.ListValues = append(d.ListValues, value)
+	return len(d.ListValues) - 1
+}
+
+func (d *Document) ImportListValue(valueRefs []int) (ref int) {
+	return d.AddListValue(ListValue{
+		Refs: valueRefs,
+	})
+}
diff --git a/pkg/ast/ast_val_object_value.go b/pkg/ast/ast_val_object_value.go
index 5d63aa7e..2ddfb666 100644
--- a/pkg/ast/ast_val_object_value.go
+++ b/pkg/ast/ast_val_object_value.go
@@ -10,3 +10,14 @@ type ObjectValue struct {
 	Refs   []int // ObjectField
 	RBRACE position.Position
 }
+
+func (d *Document) AddObjectValue(value ObjectValue) (ref int) {
+	d.ObjectValues = append(d.ObjectValues, value)
+	return len(d.ObjectValues) - 1
+}
+
+func (d *Document) ImportObjectValue(fieldRefs []int) (ref int) {
+	return d.AddObjectValue(ObjectValue{
+		Refs: fieldRefs,
+	})
+}
diff --git a/pkg/ast/ast_val_string_value.go b/pkg/ast/ast_val_string_value.go
index 023d9d8a..88b5a525 100644
--- a/pkg/ast/ast_val_string_value.go
+++ b/pkg/ast/ast_val_string_value.go
@@ -34,3 +34,15 @@ func (d *Document) StringValuesAreEquals(left, right int) bool {
 	return d.StringValueIsBlockString(left) == d.StringValueIsBlockString(right) &&
 		bytes.Equal(d.StringValueContentBytes(left), d.StringValueContentBytes(right))
 }
+
+func (d *Document) AddStringValue(value StringValue) (ref int) {
+	d.StringValues = append(d.StringValues, value)
+	return len(d.StringValues) - 1
+}
+
+func (d *Document) ImportStringValue(raw ByteSlice, isBlockString bool) (ref int) {
+	return d.AddStringValue(StringValue{
+		BlockString: isBlockString,
+		Content:     d.Input.AppendInputBytes(raw),
+	})
+}
diff --git a/pkg/ast/ast_val_variable_value.go b/pkg/ast/ast_val_variable_value.go
index 7a52db1f..e8cfc583 100644
--- a/pkg/ast/ast_val_variable_value.go
+++ b/pkg/ast/ast_val_variable_value.go
@@ -27,20 +27,13 @@ func (d *Document) VariableValuesAreEqual(left, right int) bool {
 	return bytes.Equal(d.VariableValueNameBytes(left), d.VariableValueNameBytes(right))
 }
 
-func (d *Document) AddVariableValueArgument(argName, variableName []byte) (variableValueRef, argRef int) {
-	variable := VariableValue{
-		Name: d.Input.AppendInputBytes(variableName),
-	}
-	d.VariableValues = append(d.VariableValues, variable)
-	variableValueRef = len(d.VariableValues) - 1
-	arg := Argument{
-		Name: d.Input.AppendInputBytes(argName),
-		Value: Value{
-			Kind: ValueKindVariable,
-			Ref:  variableValueRef,
-		},
-	}
-	d.Arguments = append(d.Arguments, arg)
-	argRef = len(d.Arguments) - 1
-	return
+func (d *Document) AddVariableValue(value VariableValue) (ref int) {
+	d.VariableValues = append(d.VariableValues, value)
+	return len(d.VariableValues) - 1
+}
+
+func (d *Document) ImportVariableValue(name ByteSlice) (ref int) {
+	return d.AddVariableValue(VariableValue{
+		Name: d.Input.AppendInputBytes(name),
+	})
 }
diff --git a/pkg/ast/ast_value.go b/pkg/ast/ast_value.go
index 2cf472d9..52e3ffef 100644
--- a/pkg/ast/ast_value.go
+++ b/pkg/ast/ast_value.go
@@ -155,3 +155,8 @@ func (d *Document) ValuesAreEqual(left, right Value) bool {
 		return false
 	}
 }
+
+func (d *Document) AddValue(value Value) (ref int) {
+	d.Values = append(d.Values, value)
+	return len(d.Values) - 1
+}
diff --git a/pkg/ast/index.go b/pkg/ast/index.go
index 0d5b23ee..85afba77 100644
--- a/pkg/ast/index.go
+++ b/pkg/ast/index.go
@@ -1,5 +1,7 @@
 package ast
 
+import "github.com/cespare/xxhash"
+
 // Index is a struct to easily look up objects in a document, e.g. find Nodes (type/interface/union definitions) by name
 type Index struct {
 	// QueryTypeName is the name of the query type on the schema Node
@@ -31,3 +33,7 @@ func (i *Index) Reset() {
 		delete(i.Nodes, j)
 	}
 }
+
+func (i *Index) Add(name string, node Node) {
+	i.Nodes[xxhash.Sum64String(name)] = node
+}
diff --git a/pkg/astimport/astimport.go b/pkg/astimport/astimport.go
index fdc3ee0a..4ac66d4c 100644
--- a/pkg/astimport/astimport.go
+++ b/pkg/astimport/astimport.go
@@ -35,49 +35,69 @@ func (i *Importer) ImportType(ref int, from, to *ast.Document) int {
 
 func (i *Importer) ImportValue(fromValue ast.Value, from, to *ast.Document) (value ast.Value) {
 	value.Kind = fromValue.Kind
+
 	switch fromValue.Kind {
 	case ast.ValueKindFloat:
-		to.FloatValues = append(to.FloatValues, ast.FloatValue{
-			Raw:      to.Input.AppendInputBytes(from.FloatValueRaw(fromValue.Ref)),
-			Negative: from.FloatValueIsNegative(fromValue.Ref),
-		})
-		value.Ref = len(to.FloatValues) - 1
-		return
+		value.Ref = to.ImportFloatValue(
+			from.FloatValueRaw(fromValue.Ref),
+			from.FloatValueIsNegative(fromValue.Ref))
+
 	case ast.ValueKindInteger:
-		to.FloatValues = append(to.FloatValues, ast.FloatValue{
-			Raw:      to.Input.AppendInputBytes(from.IntValueRaw(fromValue.Ref)),
-			Negative: from.IntValueIsNegative(fromValue.Ref),
-		})
-		value.Ref = len(to.IntValues) - 1
-		return
+		value.Ref = to.ImportIntValue(
+			from.IntValueRaw(fromValue.Ref),
+			from.IntValueIsNegative(fromValue.Ref))
+
 	case ast.ValueKindBoolean:
 		value.Ref = fromValue.Ref
-		return
+
 	case ast.ValueKindString:
-		to.StringValues = append(to.StringValues, ast.StringValue{
-			BlockString: from.StringValueIsBlockString(fromValue.Ref),
-			Content:     to.Input.AppendInputBytes(from.StringValueContentBytes(fromValue.Ref)),
-		})
-		return
+		value.Ref = to.ImportStringValue(
+			from.StringValueContentBytes(fromValue.Ref),
+			from.StringValueIsBlockString(fromValue.Ref))
+
 	case ast.ValueKindNull:
-		return
+		// empty case
+
 	case ast.ValueKindEnum:
-		to.EnumValues = append(to.EnumValues, ast.EnumValue{
-			Name: to.Input.AppendInputBytes(from.EnumValueNameBytes(fromValue.Ref)),
-		})
-		value.Ref = len(to.EnumValues) - 1
-		return
+		value.Ref = to.ImportEnumValue(from.EnumValueNameBytes(fromValue.Ref))
+
 	case ast.ValueKindVariable:
-		to.VariableValues = append(to.VariableValues, ast.VariableValue{
-			Name: to.Input.AppendInputBytes(from.VariableValueNameBytes(fromValue.Ref)),
-		})
-		value.Ref = len(to.VariableValues) - 1
-		return
+		value.Ref = to.ImportVariableValue(from.VariableValueNameBytes(fromValue.Ref))
+
+	case ast.ValueKindList:
+		value.Ref = to.ImportListValue(i.ImportListValues(fromValue.Ref, from, to))
+
+	case ast.ValueKindObject:
+		value.Ref = to.ImportObjectValue(i.ImportObjectFields(fromValue.Ref, from, to))
+
 	default:
 		value.Kind = ast.ValueKindUnknown
-		fmt.Printf("astimport.Importer.ImportValue: not implemented fro ValueKind: %s\n", fromValue.Kind)
-		return
+		fmt.Printf("astimport.Importer.ImportValue: not implemented for ValueKind: %s\n", fromValue.Kind)
+	}
+	return
+}
+
+func (i *Importer) ImportObjectFields(ref int, from, to *ast.Document) (refs []int) {
+	objValue := from.ObjectValues[ref]
+
+	for _, fieldRef := range objValue.Refs {
+		objectField := from.ObjectFields[fieldRef]
+
+		refs = append(refs, to.ImportObjectField(
+			from.ObjectFieldNameBytes(fieldRef),
+			i.ImportValue(objectField.Value, from, to)))
+	}
+	return
+}
+
+func (i *Importer) ImportListValues(ref int, from, to *ast.Document) (refs []int) {
+	listValue := from.ListValues[ref]
+
+	for _, valueRef := range listValue.Refs {
+		value := i.ImportValue(from.Values[valueRef], from, to)
+		refs = append(refs, to.AddValue(value))
 	}
+	return
 }
 
 func (i *Importer) ImportArgument(ref int, from, to *ast.Document) int {
diff --git a/pkg/astparser/parser.go b/pkg/astparser/parser.go
index 2e3eadd1..251438fe 100644
--- a/pkg/astparser/parser.go
+++ b/pkg/astparser/parser.go
@@ -558,7 +558,7 @@ Loop:
 
 		name := p.next()
 		colon := p.mustRead(keyword.COLON)
-		value := p.parseValue()
+		value := p.ParseValue()
 
 		argument := ast.Argument{
 			Name:  p.tokens[name].Literal,
@@ -588,7 +588,7 @@ Loop:
 	return
 }
 
-func (p *Parser) parseValue() (value ast.Value) {
+func (p *Parser) ParseValue() (value ast.Value) {
 
 	next, literal := p.peekLiteral()
 
@@ -665,7 +665,7 @@ func (p *Parser) parseObjectField() int {
 	objectField := ast.ObjectField{
 		Name:  p.mustRead(keyword.IDENT).Literal,
 		Colon: p.mustRead(keyword.COLON).TextPosition,
-		Value: p.parseValue(),
+		Value: p.ParseValue(),
 	}
 	p.document.ObjectFields = append(p.document.ObjectFields, objectField)
 	return len(p.document.ObjectFields) - 1
@@ -683,7 +683,7 @@ func (p *Parser) parseValueList() int {
 			p.document.ListValues = append(p.document.ListValues, list)
 			return len(p.document.ListValues) - 1
 		default:
-			value := p.parseValue()
+			value := p.ParseValue()
 			p.document.Values = append(p.document.Values, value)
 			ref := len(p.document.Values) - 1
 			if cap(list.Refs) == 0 {
@@ -1130,7 +1130,7 @@ func (p *Parser) parseInputValueDefinition() int {
 		equals := p.read()
 		inputValueDefinition.DefaultValue.IsDefined = true
 		inputValueDefinition.DefaultValue.Equals = equals.TextPosition
-		inputValueDefinition.DefaultValue.Value = p.parseValue()
+		inputValueDefinition.DefaultValue.Value = p.ParseValue()
 	}
 	if p.peekEquals(keyword.AT) {
 		inputValueDefinition.Directives = p.parseDirectiveList()
@@ -1726,7 +1726,7 @@ func (p *Parser) parseVariableDefinition() int {
 
 func (p *Parser) parseDefaultValue() ast.DefaultValue {
 	equals := p.mustRead(keyword.EQUALS).TextPosition
-	value := p.parseValue()
+	value := p.ParseValue()
 	return ast.DefaultValue{
 		IsDefined: true,
 		Equals:    equals,
diff --git a/pkg/astprinter/astprinter.go b/pkg/astprinter/astprinter.go
index 544edebe..90379c39 100644
--- a/pkg/astprinter/astprinter.go
+++ b/pkg/astprinter/astprinter.go
@@ -491,7 +491,16 @@ func (p *printVisitor) LeaveInputValueDefinition(ref int) {
 		}
 		p.write(p.inputValueDefinitionCloser)
 	} else {
-		p.write(literal.SPACE)
+		if len(p.Ancestors) > 0 {
+			// check enclosing type kind
+			if p.Ancestors[len(p.Ancestors)-1].Kind == ast.NodeKindFieldDefinition {
+				p.write(literal.COMMA)
+				p.write(literal.SPACE)
+			} else if len(p.indent) == 0 {
+				// add space between arguments when printing without indents
+				p.write(literal.SPACE)
+			}
+		}
 	}
 }
 
diff --git a/pkg/astprinter/fixtures/introspectionquery.golden b/pkg/astprinter/fixtures/introspectionquery.golden
index 0e3f41cf..1bf344a1 100644
--- a/pkg/astprinter/fixtures/introspectionquery.golden
+++ b/pkg/astprinter/fixtures/introspectionquery.golden
@@ -174,7 +174,7 @@ extend type Foo @foo {
 }
 
 input Foo {
-    field: String 
+    field: String
     field2: Boolean = true
 }
 
diff --git a/pkg/astprinter/fixtures/starwars_schema_definition.golden b/pkg/astprinter/fixtures/starwars_schema_definition.golden
index 577bd5cf..236ea0bd 100644
--- a/pkg/astprinter/fixtures/starwars_schema_definition.golden
+++ b/pkg/astprinter/fixtures/starwars_schema_definition.golden
@@ -27,7 +27,7 @@ extend type Query {
 
 "The mutation type, represents all updates we can make to our data"
 type Mutation {
-    createReview(episode: Episode review: ReviewInput!): Review
+    createReview(episode: Episode, review: ReviewInput!): Review
 }
 
 "The subscription type, represents all subscriptions we can make to our data"
@@ -64,7 +64,7 @@ interface Character {
     "The friends of the character, or an empty list if they have none"
     friends: [Character]
     "The friends of the character exposed as a connection with edges"
-    friendsConnection(first: Int after: ID): FriendsConnection!
+    friendsConnection(first: Int, after: ID): FriendsConnection!
     "The movies this character appears in"
     appearsIn: [Episode]!
 }
@@ -77,7 +77,7 @@ extend interface Character {
     "The friends of the character, or an empty list if they have none"
     friends: [Character]
     "The friends of the character exposed as a connection with edges"
-    friendsConnection(first: Int after: ID): FriendsConnection!
+    friendsConnection(first: Int, after: ID): FriendsConnection!
     "The movies this character appears in"
     appearsIn: [Episode]!
 }
@@ -105,7 +105,7 @@ type Human implements Character {
     "This human's friends, or an empty list if they have none"
     friends: [Character]
     "The friends of the human exposed as a connection with edges"
-    friendsConnection(first: Int after: ID): FriendsConnection!
+    friendsConnection(first: Int, after: ID): FriendsConnection!
     "The movies this human appears in"
     appearsIn: [Episode]!
     "A list of starships this person has piloted, or an empty list if none"
@@ -121,7 +121,7 @@ type Droid implements Character {
     "This droid's friends, or an empty list if they have none"
     friends: [Character]
     "The friends of the droid exposed as a connection with edges"
-    friendsConnection(first: Int after: ID): FriendsConnection!
+    friendsConnection(first: Int, after: ID): FriendsConnection!
     "The movies this droid appears in"
     appearsIn: [Episode]!
     "This droid's primary function"
@@ -168,17 +168,17 @@ type Review {
 "The input object sent when someone is creating a new review"
 input ReviewInput {
     "0-5 stars"
-    stars: Int! 
+    stars: Int!
     "Comment about the movie, optional"
-    commentary: String 
+    commentary: String
     "Favorite color, optional"
     favorite_color: ColorInput
 }
 
 "The input object sent when passing in a color"
 input ColorInput {
-    red: Int! 
-    green: Int! 
+    red: Int!
+    green: Int!
     blue: Int!
 }
 
diff --git a/pkg/execution/fixtures/introspection_execution.golden b/pkg/execution/fixtures/introspection_execution.golden
index 1160a4a5..8ceb7c14 100644
--- a/pkg/execution/fixtures/introspection_execution.golden
+++ b/pkg/execution/fixtures/introspection_execution.golden
@@ -1,1 +1,1 @@
-{"data":{"__schema":{"queryType":{"name":"Query"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"OBJECT","name":"Query","description":null,"fields":[{"name":"foo","description":"multiline\n\t\t\tdescription","args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":null,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"Int","description":null,"fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"Float","description":null,"fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"String","description":null,"fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"Boolean","description":null,"fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"ID","description":null,"fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]}],"directives":[{"name":"include","description":"Directs the executor to include this field or fragment only when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Included when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"skip","description":"Directs the executor to skip this field or fragment when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Skipped when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"deprecated","description":"Marks an element of a GraphQL schema as no longer supported.","locations":["FIELD_DEFINITION","ENUM_VALUE"],"args":[{"name":"reason","description":"Explains why this element was deprecated, usually also including a suggestion\n    for how to access supported similar data. Formatted in\n    [Markdown](https://daringfireball.net/projects/markdown/).","type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":"\"No longer supported\""}]}]}}}
\ No newline at end of file
+{"data":{"__schema":{"queryType":{"name":"Query"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"OBJECT","name":"Query","description":null,"fields":[{"name":"foo","description":"multiline\n\t\t\tdescription","args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":null,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"Int","description":"The 'Int' scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.","fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"Float","description":"The 'Float' scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).","fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"String","description":"The 'String' scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.","fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"Boolean","description":"The 'Boolean' scalar type represents 'true' or 'false' .","fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]},{"kind":"SCALAR","name":"ID","description":"The 'ID' scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as '4') or integer (such as 4) input value will be accepted as an ID.","fields":[],"inputFields":[],"interfaces":[],"enumValues":[],"possibleTypes":[]}],"directives":[{"name":"include","description":"Directs the executor to include this field or fragment only when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Included when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"skip","description":"Directs the executor to skip this field or fragment when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Skipped when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"deprecated","description":"Marks an element of a GraphQL schema as no longer supported.","locations":["FIELD_DEFINITION","ENUM_VALUE"],"args":[{"name":"reason","description":"Explains why this element was deprecated, usually also including a suggestion\n    for how to access supported similar data. Formatted in\n    [Markdown](https://daringfireball.net/projects/markdown/).","type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":"\"No longer supported\""}]}]}}}
\ No newline at end of file
diff --git a/pkg/introspection/converter.go b/pkg/introspection/converter.go
new file mode 100644
index 00000000..0e813327
--- /dev/null
+++ b/pkg/introspection/converter.go
@@ -0,0 +1,256 @@
+package introspection
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+
+	"github.com/jensneuse/graphql-go-tools/pkg/ast"
+	"github.com/jensneuse/graphql-go-tools/pkg/astimport"
+	"github.com/jensneuse/graphql-go-tools/pkg/astparser"
+	"github.com/jensneuse/graphql-go-tools/pkg/operationreport"
+)
+
+type JsonConverter struct {
+	schema *Schema
+	doc    *ast.Document
+	parser *astparser.Parser
+}
+
+func (j *JsonConverter) GraphQLDocument(introspectionJSON io.Reader) (*ast.Document, error) {
+	var data Data
+	if err := json.NewDecoder(introspectionJSON).Decode(&data); err != nil {
+		return nil, fmt.Errorf("failed to parse inrospection json: %v", err)
+	}
+
+	j.schema = &data.Schema
+	j.doc = ast.NewDocument()
+	j.parser = astparser.NewParser()
+
+	if err := j.importSchema(); err != nil {
+		return nil, fmt.Errorf("failed to convert graphql schema: %v", err)
+	}
+
+	return j.doc, nil
+}
+
+func (j *JsonConverter) importSchema() error {
+	j.doc.ImportSchemaDefinition(j.schema.TypeNames())
+
+	for i := 0; i < len(j.schema.Types); i++ {
+		if err := j.importFullType(j.schema.Types[i]); err != nil {
+			return err
+		}
+	}
+
+	for i := 0; i < len(j.schema.Directives); i++ {
+		if err := j.importDirective(j.schema.Directives[i]); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (j *JsonConverter) importFullType(fullType FullType) (err error) {
+	switch fullType.Kind {
+	case SCALAR:
+		j.doc.ImportScalarTypeDefinition(fullType.Name, fullType.Description)
+	case OBJECT:
+		err = j.importObject(fullType)
+	case ENUM:
+		j.importEnum(fullType)
+	case INTERFACE:
+		err = j.importInterface(fullType)
+	case UNION:
+		err = j.importUnion(fullType)
+	case INPUTOBJECT:
+		err = j.importInputObject(fullType)
+	}
+	return
+}
+
+func (j *JsonConverter) importObject(fullType FullType) error {
+	fieldRefs, err := j.importFields(fullType.Fields)
+	if err != nil {
+		return err
+	}
+
+	iRefs := make([]int, len(fullType.Interfaces))
+	for i := 0; i < len(iRefs); i++ {
+		iRefs[i] = j.importType(fullType.Interfaces[i])
+	}
+
+	j.doc.ImportObjectTypeDefinition(
+		fullType.Name,
+		fullType.Description,
+		fieldRefs,
+		iRefs)
+
+	return nil
+}
+
+func (j *JsonConverter) importInterface(fullType FullType) error {
+	fieldRefs, err := j.importFields(fullType.Fields)
+	if err != nil {
+		return err
+	}
+
+	j.doc.ImportInterfaceTypeDefinition(
+		fullType.Name,
+		fullType.Description,
+		fieldRefs)
+
+	return nil
+}
+
+func (j *JsonConverter) importDirective(directive Directive) error {
+	argRefs, err := j.importInputFields(directive.Args)
+	if err != nil {
+		return err
+	}
+
+	j.doc.ImportDirectiveDefinition(
+		directive.Name,
+		directive.Description,
+		argRefs,
+		directive.Locations)
+
+	return nil
+}
+
+func (j *JsonConverter) importInputObject(fullType FullType) error {
+	argRefs, err := j.importInputFields(fullType.InputFields)
+	if err != nil {
+		return err
+	}
+
+	j.doc.ImportInputObjectTypeDefinition(
+		fullType.Name,
+		fullType.Description,
+		argRefs)
+
+	return nil
+}
+
+func (j *JsonConverter) importEnum(fullType FullType) {
+	valueRefs := make([]int, len(fullType.EnumValues))
+	for i := 0; i < len(valueRefs); i++ {
+		valueRefs[i] = j.doc.ImportEnumValueDefinition(
+			fullType.EnumValues[i].Name,
+			fullType.EnumValues[i].Description,
+		)
+	}
+
+	j.doc.ImportEnumTypeDefinition(
+		fullType.Name,
+		fullType.Description,
+		valueRefs)
+}
+
+func (j *JsonConverter) importUnion(fullType FullType) error {
+	typeRefs := make([]int, len(fullType.PossibleTypes))
+	for i := 0; i < len(typeRefs); i++ {
+		typeRefs[i] = j.importType(fullType.PossibleTypes[i])
+	}
+
+	j.doc.ImportUnionTypeDefinition(
+		fullType.Name,
+		fullType.Description,
+		typeRefs)
+
+	return nil
+}
+
+func (j *JsonConverter) importFields(fields []Field) (refs []int, err error) {
+	refs = make([]int, len(fields))
+	for i := 0; i < len(refs); i++ {
+		fieldRef, err := j.importField(fields[i])
+		if err != nil {
+			return nil, err
+		}
+		refs[i] = fieldRef
+	}
+
+	return
+}
+
+func (j *JsonConverter) importField(field Field) (ref int, err error) {
+	typeRef := j.importType(field.Type)
+
+	argRefs, err := j.importInputFields(field.Args)
+	if err != nil {
+		return -1, err
+	}
+
+	return j.doc.ImportFieldDefinition(
+		field.Name, field.Description, typeRef, argRefs), nil
+}
+
+func (j *JsonConverter) importInputFields(fields []InputValue) (refs []int, err error) {
+	refs = make([]int, len(fields))
+	for i := 0; i < len(refs); i++ {
+		argRef, err := j.importInputField(fields[i])
+		if err != nil {
+			return nil, err
+		}
+		refs[i] = argRef
+	}
+	return
+}
+
+func (j *JsonConverter) importInputField(field InputValue) (ref int, err error) {
+	typeRef := j.importType(field.Type)
+
+	defaultValue, err := j.importDefaultValue(field.DefaultValue)
+	if err != nil {
+		return -1, err
+	}
+
+	return j.doc.ImportInputValueDefinition(
+		field.Name, field.Description, typeRef, defaultValue), nil
+}
+
+func (j *JsonConverter) importType(typeRef TypeRef) (ref int) {
+	switch typeRef.Kind {
+	case LIST:
+		listType := ast.Type{
+			TypeKind: ast.TypeKindList,
+			OfType:   j.importType(*typeRef.OfType),
+		}
+		return j.doc.AddType(listType)
+	case NONNULL:
+		nonNullType := ast.Type{
+			TypeKind: ast.TypeKindNonNull,
+			OfType:   j.importType(*typeRef.OfType),
+		}
+		return j.doc.AddType(nonNullType)
+	}
+
+	return j.doc.AddNamedType([]byte(*typeRef.Name))
+}
+
+func (j *JsonConverter) importDefaultValue(defaultValue *string) (out ast.DefaultValue, err error) {
+	if defaultValue == nil {
+		return
+	}
+
+	from := ast.NewDocument()
+	from.Input.AppendInputString(*defaultValue)
+
+	report := &operationreport.Report{}
+
+	j.parser.PrepareImport(from, report)
+	value := j.parser.ParseValue()
+
+	if report.HasErrors() {
+		err = report
+		return
+	}
+
+	importer := &astimport.Importer{}
+	return ast.DefaultValue{
+		IsDefined: true,
+		Value:     importer.ImportValue(value, from, j.doc),
+	}, nil
+}
diff --git a/pkg/introspection/fixtures/starwars.golden b/pkg/introspection/fixtures/starwars.golden
new file mode 100644
index 00000000..718015a0
--- /dev/null
+++ b/pkg/introspection/fixtures/starwars.golden
@@ -0,0 +1,199 @@
+schema {
+    query: Query
+    mutation: Mutation
+    subscription: Subscription
+}
+
+"The query type, represents all of the entry points into our object graph"
+type Query {
+    hero(episode: Episode): Character
+    reviews(episode: Episode!): [Review]
+    search(text: String): [SearchResult]
+    character(id: ID!): Character
+    droid(id: ID!): Droid
+    human(id: ID!): Human
+    starship(id: ID!): Starship
+}
+
+"The mutation type, represents all updates we can make to our data"
+type Mutation {
+    createReview(episode: Episode, review: ReviewInput!): Review
+}
+
+"The subscription type, represents all subscriptions we can make to our data"
+type Subscription {
+    reviewAdded(episode: Episode): Review
+}
+
+"The episodes in the Star Wars trilogy"
+enum Episode {
+    "Star Wars Episode IV: A New Hope, released in 1977."
+    NEWHOPE
+    "Star Wars Episode V: The Empire Strikes Back, released in 1980."
+    EMPIRE
+    "Star Wars Episode VI: Return of the Jedi, released in 1983."
+    JEDI
+}
+
+"A character from the Star Wars universe"
+interface Character {
+    "The ID of the character"
+    id: ID!
+    "The name of the character"
+    name: String!
+    "The friends of the character, or an empty list if they have none"
+    friends: [Character]
+    "The friends of the character exposed as a connection with edges"
+    friendsConnection(first: Int, after: ID): FriendsConnection!
+    "The movies this character appears in"
+    appearsIn: [Episode]!
+}
+
+"Units of height"
+enum LengthUnit {
+    "The standard unit around the world"
+    METER
+    "Primarily used in the United States"
+    FOOT
+}
+
+"A humanoid creature from the Star Wars universe"
+type Human implements Character {
+    "The ID of the human"
+    id: ID!
+    "What this human calls themselves"
+    name: String!
+    "The home planet of the human, or null if unknown"
+    homePlanet: String
+    "Height in the preferred unit, default is meters"
+    height(unit: LengthUnit = METER): Float
+    "Mass in kilograms, or null if unknown"
+    mass: Float
+    "This human's friends, or an empty list if they have none"
+    friends: [Character]
+    "The friends of the human exposed as a connection with edges"
+    friendsConnection(first: Int, after: ID): FriendsConnection!
+    "The movies this human appears in"
+    appearsIn: [Episode]!
+    "A list of starships this person has piloted, or an empty list if none"
+    starships: [Starship]
+}
+
+"An autonomous mechanical character in the Star Wars universe"
+type Droid implements Character {
+    "The ID of the droid"
+    id: ID!
+    "What others call this droid"
+    name: String!
+    "This droid's friends, or an empty list if they have none"
+    friends: [Character]
+    "The friends of the droid exposed as a connection with edges"
+    friendsConnection(first: Int, after: ID): FriendsConnection!
+    "The movies this droid appears in"
+    appearsIn: [Episode]!
+    "This droid's primary function"
+    primaryFunction: String
+}
+
+"A connection object for a character's friends"
+type FriendsConnection {
+    "The total number of friends"
+    totalCount: Int
+    "The edges for each of the character's friends."
+    edges: [FriendsEdge]
+    "A list of the friends, as a convenience when edges are not needed."
+    friends: [Character]
+    "Information for paginating this connection"
+    pageInfo: PageInfo!
+}
+
+"An edge object for a character's friends"
+type FriendsEdge {
+    "A cursor used for pagination"
+    cursor: ID!
+    "The character represented by this friendship edge"
+    node: Character
+}
+
+"Information for paginating this connection"
+type PageInfo {
+    startCursor: ID
+    endCursor: ID
+    hasNextPage: Boolean!
+}
+
+"Represents a review for a movie"
+type Review {
+    "The movie"
+    episode: Episode
+    "The number of stars this review gave, 1-5"
+    stars: Int!
+    "Comment about the movie"
+    commentary: String
+}
+
+"The input object sent when someone is creating a new review"
+input ReviewInput {
+    "0-5 stars"
+    stars: Int!
+    "Comment about the movie, optional"
+    commentary: String
+    "Favorite color, optional"
+    favorite_color: ColorInput
+}
+
+"The input object sent when passing in a color"
+input ColorInput {
+    red: Int!
+    green: Int!
+    blue: Int!
+}
+
+type Starship {
+    "The ID of the starship"
+    id: ID!
+    "The name of the starship"
+    name: String!
+    "Length of the starship, along the longest axis"
+    length(unit: LengthUnit = METER): Float
+}
+
+"The union represents combined return result which could be on of the types: Human, Droid, Starship"
+union SearchResult = Human | Droid | Starship
+
+"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
+scalar Int
+
+"The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)."
+scalar Float
+
+"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
+scalar String
+
+"The `Boolean` scalar type represents `true` or `false` ."
+scalar Boolean
+
+"The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `4`) or integer (such as 4) input value will be accepted as an ID."
+scalar ID
+
+"Directs the executor to include this field or fragment only when the argument is true."
+directive @include(
+    "Included when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Directs the executor to skip this field or fragment when the argument is true."
+directive @skip(
+    "Skipped when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Marks an element of a GraphQL schema as no longer supported."
+directive @deprecated(
+    """
+    Explains why this element was deprecated, usually also including a suggestion
+    for how to access supported similar data. Formatted in
+    [Markdown](https://daringfireball.net/projects/markdown/).
+    """
+    reason: String = "No longer supported"
+) on FIELD_DEFINITION | ENUM_VALUE
\ No newline at end of file
diff --git a/pkg/introspection/fixtures/startwars_introspected.golden b/pkg/introspection/fixtures/starwars_introspected.golden
similarity index 95%
rename from pkg/introspection/fixtures/startwars_introspected.golden
rename to pkg/introspection/fixtures/starwars_introspected.golden
index 58102081..5de1c6b3 100644
--- a/pkg/introspection/fixtures/startwars_introspected.golden
+++ b/pkg/introspection/fixtures/starwars_introspected.golden
@@ -401,7 +401,18 @@
         "inputFields": [],
         "interfaces": [],
         "enumValues": [],
-        "possibleTypes": []
+        "possibleTypes": [
+          {
+            "kind": "OBJECT",
+            "name": "Human",
+            "ofType": null
+          },
+          {
+            "kind": "OBJECT",
+            "name": "Droid",
+            "ofType": null
+          }
+        ]
       },
       {
         "kind": "ENUM",
@@ -902,7 +913,7 @@
       {
         "kind": "INPUT_OBJECT",
         "name": "ReviewInput",
-        "description": "ReviewInput",
+        "description": "The input object sent when someone is creating a new review",
         "fields": [],
         "inputFields": [
           {
@@ -947,7 +958,7 @@
       {
         "kind": "INPUT_OBJECT",
         "name": "ColorInput",
-        "description": "ColorInput",
+        "description": "The input object sent when passing in a color",
         "fields": [],
         "inputFields": [
           {
@@ -1086,7 +1097,7 @@
       {
         "kind": "SCALAR",
         "name": "Int",
-        "description": "",
+        "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",
         "fields": [],
         "inputFields": [],
         "interfaces": [],
@@ -1096,7 +1107,7 @@
       {
         "kind": "SCALAR",
         "name": "Float",
-        "description": "",
+        "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).",
         "fields": [],
         "inputFields": [],
         "interfaces": [],
@@ -1106,7 +1117,7 @@
       {
         "kind": "SCALAR",
         "name": "String",
-        "description": "",
+        "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
         "fields": [],
         "inputFields": [],
         "interfaces": [],
@@ -1116,7 +1127,7 @@
       {
         "kind": "SCALAR",
         "name": "Boolean",
-        "description": "",
+        "description": "The `Boolean` scalar type represents `true` or `false` .",
         "fields": [],
         "inputFields": [],
         "interfaces": [],
@@ -1126,7 +1137,7 @@
       {
         "kind": "SCALAR",
         "name": "ID",
-        "description": "",
+        "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `4`) or integer (such as 4) input value will be accepted as an ID.",
         "fields": [],
         "inputFields": [],
         "interfaces": [],
diff --git a/pkg/introspection/generator.go b/pkg/introspection/generator.go
index 7aaec87f..ad08f365 100644
--- a/pkg/introspection/generator.go
+++ b/pkg/introspection/generator.go
@@ -98,7 +98,6 @@ func (i *introspectionVisitor) LeaveFieldDefinition(ref int) {
 }
 
 func (i *introspectionVisitor) EnterInputValueDefinition(ref int) {
-
 	var defaultValue *string
 	if i.definition.InputValueDefinitionHasDefaultValue(ref) {
 		value := i.definition.InputValueDefinitionDefaultValue(ref)
@@ -137,6 +136,17 @@ func (i *introspectionVisitor) EnterInterfaceTypeDefinition(ref int) {
 	i.currentType.Kind = INTERFACE
 	i.currentType.Name = i.definition.InterfaceTypeDefinitionNameString(ref)
 	i.currentType.Description = i.definition.InterfaceTypeDefinitionDescriptionString(ref)
+
+	interfaceNameBytes := i.definition.InterfaceTypeDefinitionNameBytes(ref)
+	for objectTypeDefRef := range i.definition.ObjectTypeDefinitions {
+		if i.definition.ObjectTypeDefinitionImplementsInterface(objectTypeDefRef, interfaceNameBytes) {
+			objectName := i.definition.ObjectTypeDefinitionNameString(objectTypeDefRef)
+			i.currentType.PossibleTypes = append(i.currentType.PossibleTypes, TypeRef{
+				Kind: OBJECT,
+				Name: &objectName,
+			})
+		}
+	}
 }
 
 func (i *introspectionVisitor) LeaveInterfaceTypeDefinition(ref int) {
@@ -158,6 +168,7 @@ func (i *introspectionVisitor) EnterScalarTypeDefinition(ref int) {
 	typeDefinition := NewFullType()
 	typeDefinition.Kind = SCALAR
 	typeDefinition.Name = i.definition.ScalarTypeDefinitionNameString(ref)
+	typeDefinition.Description = i.definition.ScalarTypeDefinitionDescriptionString(ref)
 	i.data.Schema.Types = append(i.data.Schema.Types, typeDefinition)
 }
 
diff --git a/pkg/introspection/introspection.go b/pkg/introspection/introspection.go
index 86af8df5..090ee534 100644
--- a/pkg/introspection/introspection.go
+++ b/pkg/introspection/introspection.go
@@ -19,6 +19,19 @@ type Schema struct {
 	Directives       []Directive `json:"directives"`
 }
 
+func (s *Schema) TypeNames() (query, mutation, subscription string) {
+	if s.QueryType != nil {
+		query = s.QueryType.Name
+	}
+	if s.MutationType != nil {
+		mutation = s.MutationType.Name
+	}
+	if s.SubscriptionType != nil {
+		subscription = s.SubscriptionType.Name
+	}
+	return
+}
+
 func NewSchema() Schema {
 	return Schema{
 		Types:      make([]FullType, 0),
@@ -31,14 +44,19 @@ type TypeName struct {
 }
 
 type FullType struct {
-	Kind          __TypeKind   `json:"kind"`
-	Name          string       `json:"name"`
-	Description   string       `json:"description"`
-	Fields        []Field      `json:"fields"`
-	InputFields   []InputValue `json:"inputFields"`
-	Interfaces    []TypeRef    `json:"interfaces"`
-	EnumValues    []EnumValue  `json:"enumValues"`
-	PossibleTypes []TypeRef    `json:"possibleTypes"`
+	Kind        __TypeKind `json:"kind"`
+	Name        string     `json:"name"`
+	Description string     `json:"description"`
+	// not empty for __TypeKind OBJECT and INTERFACE only
+	Fields []Field `json:"fields"`
+	// not empty for __TypeKind INPUT_OBJECT only
+	InputFields []InputValue `json:"inputFields"`
+	// not empty for __TypeKind OBJECT only
+	Interfaces []TypeRef `json:"interfaces"`
+	// not empty for __TypeKind ENUM only
+	EnumValues []EnumValue `json:"enumValues"`
+	// not empty for __TypeKind INTERFACE and UNION only
+	PossibleTypes []TypeRef `json:"possibleTypes"`
 }
 
 func NewFullType() FullType {
@@ -97,8 +115,8 @@ type Field struct {
 	Description string       `json:"description"`
 	Args        []InputValue `json:"args"`
 	Type        TypeRef      `json:"type"`
-	//IsDeprecated      *bool        `json:"isDeprecated"`
-	//DeprecationReason string       `json:"deprecationReason"`
+	// IsDeprecated      *bool        `json:"isDeprecated"`
+	// DeprecationReason string       `json:"deprecationReason"`
 }
 
 func NewField() Field {
@@ -110,8 +128,8 @@ func NewField() Field {
 type EnumValue struct {
 	Name        string `json:"name"`
 	Description string `json:"description"`
-	//IsDeprecated      *bool   `json:"isDeprecated"`
-	//DeprecationReason *string `json:"deprecationReason"`
+	// IsDeprecated      *bool   `json:"isDeprecated"`
+	// DeprecationReason *string `json:"deprecationReason"`
 }
 
 type InputValue struct {


Test patch:
diff --git a/pkg/astimport/astimport_test.go b/pkg/astimport/astimport_test.go
index be9bd7d5..8ff20b32 100644
--- a/pkg/astimport/astimport_test.go
+++ b/pkg/astimport/astimport_test.go
@@ -1,16 +1,16 @@
 package astimport
 
 import (
-	"bytes"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/jensneuse/graphql-go-tools/pkg/ast"
 	"github.com/jensneuse/graphql-go-tools/pkg/astparser"
 	"github.com/jensneuse/graphql-go-tools/pkg/operationreport"
 )
 
 func TestImporter_ImportType(t *testing.T) {
-
 	for _, typeDef := range []string{
 		"ID!",
 		"[String]!",
@@ -18,7 +18,7 @@ func TestImporter_ImportType(t *testing.T) {
 		"FooType",
 	} {
 		typeBytes := []byte(typeDef)
-		from := &ast.Document{}
+		from := ast.NewDocument()
 		from.Input.AppendInputBytes(typeBytes)
 		report := &operationreport.Report{}
 		parser := astparser.NewParser()
@@ -30,43 +30,65 @@ func TestImporter_ImportType(t *testing.T) {
 		}
 
 		out, err := from.PrintTypeBytes(ref, nil)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if !bytes.Equal(out, typeBytes) {
-			t.Fatalf("want: {{%s}}\ngot: {{%s}}'", string(typeBytes), string(out))
-		}
+		require.NoError(t, err)
+		require.Equal(t, typeBytes, out)
 
-		to := &ast.Document{}
+		to := ast.NewDocument()
 		importer := &Importer{}
 
 		ref = importer.ImportType(ref, from, to)
 		out, err = to.PrintTypeBytes(ref, nil)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if !bytes.Equal(out, typeBytes) {
-			t.Fatalf("want: {{%s}}\ngot: {{%s}}'", string(typeBytes), string(out))
-		}
+		require.NoError(t, err)
+		require.Equal(t, typeBytes, out)
 	}
 }
 
-/*func ExampleImporter_ImportType() {
-	typeBytes := []byte("String!")
-	doc := ast.Document{}
-	importer := NewImporter()
-
-	ref := importer.ImportTypeBytes(typeBytes,&doc)
-	// reference to the type
-	fmt.Println(ref)
-	// reference to the nested type
-	fmt.Println(doc.Types[ref].OfType)
-	// name of the nested type
-	fmt.Println(doc.TypeNameString(doc.Types[ref].OfType))
-	// Output:
-	// 1
-	// 0
-	// String
-}*/
+func TestImporter_ImportValue(t *testing.T) {
+	for _, tc := range []struct {
+		value, name string
+		kind        ast.ValueKind
+	}{
+		{"111", "integer", ast.ValueKindInteger},
+		{"-111", "negative integer", ast.ValueKindInteger},
+		{"11.1", "float", ast.ValueKindFloat},
+		{"-11.1", "negative float", ast.ValueKindFloat},
+		{`"bobby"`, "string", ast.ValueKindString},
+		{"ENUM_VALUE", "enum", ast.ValueKindEnum},
+		{`{one: "one"}`, "object", ast.ValueKindObject},
+		{"true", "bool", ast.ValueKindBoolean},
+		{"[1,2]", "list", ast.ValueKindList},
+		{"[[1,2]]", "nested list", ast.ValueKindList},
+		{`[{a: "b"},{c: "d"}]`, "list with objects", ast.ValueKindList},
+		{`[[{a: "b",c: [1,2]}]]`, "deep nested list", ast.ValueKindList},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			valueBytes := []byte(tc.value)
+
+			from := ast.NewDocument()
+			from.Input.AppendInputBytes(valueBytes)
+
+			report := &operationreport.Report{}
+			parser := astparser.NewParser()
+			parser.PrepareImport(from, report)
+
+			value := parser.ParseValue()
+			require.Equal(t, value.Kind, tc.kind)
+
+			if report.HasErrors() {
+				t.Fatal(report)
+			}
+
+			out, err := from.PrintValueBytes(value, nil)
+			require.NoError(t, err)
+			require.Equal(t, valueBytes, out)
+
+			to := ast.NewDocument()
+			importer := &Importer{}
+
+			value = importer.ImportValue(value, from, to)
+			out, err = to.PrintValueBytes(value, nil)
+			require.NoError(t, err)
+			require.Equal(t, valueBytes, out)
+		})
+	}
+}
diff --git a/pkg/astparser/parser_test.go b/pkg/astparser/parser_test.go
index 315b21b8..56f475d4 100644
--- a/pkg/astparser/parser_test.go
+++ b/pkg/astparser/parser_test.go
@@ -43,7 +43,7 @@ func TestParser_Parse(t *testing.T) {
 			parser.report = &report
 			parser.lexer.SetInput(&parser.document.Input)
 			parser.tokenize()
-			value := parser.parseValue()
+			value := parser.ParseValue()
 			return value, report
 		}
 	}
diff --git a/pkg/astprinter/astprinter_test.go b/pkg/astprinter/astprinter_test.go
index 559e3fbe..4134c842 100644
--- a/pkg/astprinter/astprinter_test.go
+++ b/pkg/astprinter/astprinter_test.go
@@ -2,12 +2,12 @@ package astprinter
 
 import (
 	"bytes"
-	"fmt"
 	"io/ioutil"
 	"testing"
 
 	"github.com/jensneuse/diffview"
 	"github.com/sebdah/goldie"
+	"github.com/stretchr/testify/assert"
 
 	"github.com/jensneuse/graphql-go-tools/internal/pkg/unsafeparser"
 	"github.com/jensneuse/graphql-go-tools/pkg/operationreport"
@@ -27,7 +27,7 @@ func TestPrint(t *testing.T) {
 		}
 	}
 
-	run := func(raw string, want string) {
+	run := func(raw string, expected string) {
 
 		definition := unsafeparser.ParseGraphqlDocumentString(testDefinition)
 		doc := unsafeparser.ParseGraphqlDocumentString(raw)
@@ -37,11 +37,8 @@ func TestPrint(t *testing.T) {
 
 		must(printer.Print(&doc, &definition, buff))
 
-		got := buff.String()
-
-		if want != got {
-			panic(fmt.Errorf("want:\n%s\ngot:\n%s\n", want, got))
-		}
+		actual := buff.String()
+		assert.Equal(t, expected, actual)
 	}
 
 	t.Run("simple", func(t *testing.T) {
diff --git a/pkg/introspection/converter_test.go b/pkg/introspection/converter_test.go
new file mode 100644
index 00000000..17656e85
--- /dev/null
+++ b/pkg/introspection/converter_test.go
@@ -0,0 +1,76 @@
+package introspection
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"testing"
+
+	"github.com/jensneuse/diffview"
+	"github.com/sebdah/goldie"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/jensneuse/graphql-go-tools/pkg/astparser"
+	"github.com/jensneuse/graphql-go-tools/pkg/astprinter"
+)
+
+func TestJSONConverter_GraphQLDocument(t *testing.T) {
+	starwarsSchemaBytes, err := ioutil.ReadFile("./fixtures/starwars.golden")
+	require.NoError(t, err)
+
+	definition, report := astparser.ParseGraphqlDocumentBytes(starwarsSchemaBytes)
+	if report.HasErrors() {
+		t.Fatal(report)
+	}
+
+	gen := NewGenerator()
+	var data Data
+	gen.Generate(&definition, &report, &data)
+	if report.HasErrors() {
+		t.Fatal(report)
+	}
+
+	outputPretty, err := json.MarshalIndent(data, "", "  ")
+	require.NoError(t, err)
+
+	converter := JsonConverter{}
+	buf := bytes.NewBuffer(outputPretty)
+	doc, err := converter.GraphQLDocument(buf)
+	assert.NoError(t, err)
+
+	outWriter := &bytes.Buffer{}
+	err = astprinter.PrintIndent(doc, nil, []byte("  "), outWriter)
+	require.NoError(t, err)
+
+	schemaOutputPretty := outWriter.Bytes()
+	// fmt.Println(string(schemaOutputPretty))
+	// ioutil.WriteFile("./fixtures/starwars_generated.graphql", schemaOutputPretty, os.ModePerm)
+
+	// Ensure that recreated sdl is valid
+	definition, report = astparser.ParseGraphqlDocumentBytes(schemaOutputPretty)
+	if report.HasErrors() {
+		t.Fatal(report)
+	}
+
+	// Check that recreated sdl is the same as original
+	goldie.Assert(t, "starwars", schemaOutputPretty)
+	if t.Failed() {
+		fixture, err := ioutil.ReadFile("./fixtures/starwars.golden")
+		require.NoError(t, err)
+
+		diffview.NewGoland().DiffViewBytes("startwars", fixture, schemaOutputPretty)
+	}
+}
+
+func BenchmarkJsonConverter_GraphQLDocument(b *testing.B) {
+	introspectedBytes, err := ioutil.ReadFile("./testdata/swapi_introspection_response.json")
+	require.NoError(b, err)
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		buf := bytes.NewBuffer(introspectedBytes)
+		converter := JsonConverter{}
+		_, _ = converter.GraphQLDocument(buf)
+	}
+}
diff --git a/pkg/introspection/generator_test.go b/pkg/introspection/generator_test.go
index 525614ce..ebc19578 100644
--- a/pkg/introspection/generator_test.go
+++ b/pkg/introspection/generator_test.go
@@ -34,9 +34,9 @@ func TestGenerator_Generate(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	goldie.Assert(t, "startwars_introspected", outputPretty)
+	goldie.Assert(t, "starwars_introspected", outputPretty)
 	if t.Failed() {
-		fixture, err := ioutil.ReadFile("./fixtures/startwars_introspected.golden")
+		fixture, err := ioutil.ReadFile("./fixtures/starwars_introspected.golden")
 		if err != nil {
 			t.Fatal(err)
 		}