Define a dgraph go struct with other tag !='json'

Here is a go struct , I want to use the json tag for user side , and the dgraph tag for dgraph side(for query and mutate data).
Is there any suggestion?

type Person struct{
    Name    string       `json:"name,omitempty" dgraph:"name,omitempty"`
    Friends  []*Person `json:"friends,omitempty" dgraph:"friends,omitempty"`
    Friendsof []*Persion `json:"friendsof,omitempty" dgraph:"~friends,omitempty"`
}

I wrote something similar for GraphQL:

You are free to take this and adapt it to DQL. The tag that I used for lookup is gql. You can switch that to dgraph.


import (
	"bytes"
	"encoding"
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/pkg/errors"
)

type Op int

const (
	Create Op = 1 << iota
	Read
	Update
	Delete
)

type state struct {
	bytes.Buffer // out
	embedded     bool
	depth        int
	scratch      [64]byte
	tmp          *bytes.Buffer
}

func newState() *state { s := &state{}; s.tmp = bytes.NewBuffer(s.scratch[:]); return s }

func (s *state) encodeUID(uid uint64) {
	fmt.Fprintf(&s.Buffer, "\"%#x\"", uid)
}

func (s *state) encodeString(str string) {
	fmt.Fprintf(&s.Buffer, "%q", str)
}

func (s *state) encodeUint(n uint64)   { fmt.Fprintf(s, "%d", n) }
func (s *state) encodeInt(n int64)     { fmt.Fprintf(s, "%d", n) }
func (s *state) encodeFloat(n float64) { fmt.Fprintf(s, "%f", n) }

func Marshal(a interface{}, op Op) ([]byte, error) {
	s := newState()

	aT := reflect.TypeOf(a)
	switch aT.Kind() {
	case reflect.Ptr:
		// for root, we simply do this.
		aV := reflect.ValueOf(a)
		a = aV.Elem().Interface()
		return s.marshalNP(a, op)
	case reflect.Slice:
		el := aT.Elem()
		isPtr := el.Kind() == reflect.Ptr
		return s.marshalS(a, op, isPtr)
	}

	return s.marshalNP(a, op)
}

// marshalName puts the name in a temporary buffer
func (s *state) marshalName(name string) {
	s.tmp.WriteString(name)
	s.tmp.WriteByte(':')
}
func (s *state) resetTmp() { s.tmp.Truncate(0) }

func (s *state) marshalS(a interface{}, op Op, isPtr bool) ([]byte, error) {
	aV := reflect.ValueOf(a)
	s.WriteByte('[')
	for i := 0; i < aV.Len(); i++ {
		iface := aV.Index(i).Interface()
		if isPtr {
			s.marshalP(iface, Update)
			} else {
			s.marshalNP(iface, op)
		}

		if i < aV.Len()-1 {
			s.WriteByte(',')
		}
	}
	s.WriteByte(']')
	return s.Bytes(), nil
}

func checkNilPtr(a interface{}) bool {
	return reflect.ValueOf(a).IsNil()
}

func checkZeroLen(a interface{}) bool {
	return reflect.ValueOf(a).Len() == 0
}

func (s *state) marshalP(a interface{}, op Op) ([]byte, error) {
	aV := reflect.ValueOf(a)
	if aV.IsNil() {
		return nil, errors.New("Nil")
	}
	a = aV.Elem().Interface()
	return s.marshalNP(a, op)
}

func (s *state) marshalNP(a interface{}, op Op) ([]byte, error) {
	aT := reflect.TypeOf(a)
	aV := reflect.ValueOf(a)
	fields := aT.NumField()
	if !s.embedded {
		s.WriteByte('{')
	}

	var isUpdate bool
	if op == Update && !s.embedded && s.depth < 1 {
		isUpdate = true
		// find ID field
		var v reflect.Value
		for i := 0; i < fields; i++ {
			field := aT.Field(i)
			name := marshalledName(field)
			if isID(name) {
				v = aV.Field(i)
				break
			}
		}
		if !v.IsValid() {
			return nil, errors.Errorf("Cannot perform Update. No ID found in %v of %T", a, a)
		}
		s.WriteString("filter: {id: \"")
		s.encodeUID(v.Uint()) // panics if not uint64
		s.WriteString("\"}, set:{")
	}

	emb := s.embedded
	s.depth++
	for i := 0; i < fields; i++ {
		s.resetTmp()

		field := aT.Field(i)
		kind := field.Type.Kind()
		name := marshalledName(field)
		fieldV := aV.Field(i)

		if op == Create && isID(name) {
			continue
		}
		if isIgnored(name) {
			continue
		}
		if name != "" {
			s.marshalName(name)
		}
		s.embedded = false
		if name == "" && field.Anonymous {
			s.embedded = true
		}
	
		var tocomma bool = true
		iface := fieldV.Interface()
		switch t := iface.(type) {
		case json.Marshaler:
			mt, err := t.MarshalJSON()
			if err != nil {
				return nil, errors.Wrapf(err, "Unable to marshal field %v. Value is of %T.", name, iface)
			}
			if len(mt) > 0 {
				s.Write(s.tmp.Bytes())
				s.Write(mt)
			}
		case encoding.TextMarshaler:
			mt, err := t.MarshalText()
			if err != nil {
				return nil, errors.Wrapf(err, "Unable to marshal field %v. Value is of %T.", name, iface)
			}
			if len(mt) > 0 {
				s.Write(s.tmp.Bytes())
				s.WriteByte('"')
				s.Write(mt)
				s.WriteByte('"')
			}
		default:
			switch kind {
			case reflect.String:
				s.Write(s.tmp.Bytes())
				s.encodeString(fieldV.String())
			case reflect.Uint64:
				s.Write(s.tmp.Bytes())
				if isID(name) {
					s.encodeUID(fieldV.Uint())
				} else {
					s.encodeUint(fieldV.Uint())
				}
			case reflect.Int:
				s.Write(s.tmp.Bytes())
				s.encodeInt(fieldV.Int())
			case reflect.Struct:
				s.Write(s.tmp.Bytes())
				s.marshalNP(iface, op)
			case reflect.Ptr:
				tocomma = false
				if !checkNilPtr(iface) {
					tocomma = true
					s.Write(s.tmp.Bytes())
				}
				s.marshalP(iface, Update)
			case reflect.Slice:
				s.embedded = false
				isPtr := field.Type.Elem().Kind() == reflect.Ptr
				tocomma = false
				if !checkZeroLen(iface) {
					tocomma = true
					s.Write(s.tmp.Bytes())
					s.marshalS(iface, op, isPtr)
				}
			default:
				return nil, errors.Errorf("Unable to marshal field %v, with type %T", name, iface)
			}
		}

		if i < fields-1 && tocomma {
			s.WriteByte(',')
		}
		s.embedded = emb
	}
	s.depth--
	// retract any stray commas
	if s.Bytes()[len(s.Bytes())-1] == ',' {
		s.Truncate(len(s.Bytes()) - 1)
	}

	if !s.embedded {
		s.WriteByte('}')
	}
	if isUpdate {
		s.WriteByte('}')
	}
	return s.Bytes(), nil
}

func marshalledName(field reflect.StructField) string {
	name, ok := field.Tag.Lookup("json")
	if !ok {
		name, ok = field.Tag.Lookup("gql")
		if !ok && !field.Anonymous {
			name = field.Name
		}
	}
	return name
}

func isID(a string) bool {
	switch a {
	case "id", "ID", "UID", "uid":
		return true
	default:
		return false
	}
}

func isIgnored(a string) bool {
	return a == "-"
}

Note: marshalNP and marshalP handles marshalling of non-pointer (NP) and pointer ยง values. These are very specific to the usecase I was writing the code for. The code treats pointer values as mutable in graphql so the generated mutation is different.