package stt import ( "fmt" "github.com/modern-go/reflect2" "io" "reflect" "unsafe" ) func encoderOfStruct(ctx *ctx, typ reflect2.Type) ValEncoder { type bindingTo struct { binding *Binding toName string ignored bool } orderedBindings := []*bindingTo{} structDescriptor := describeStruct(ctx, typ) for _, binding := range structDescriptor.Fields { for _, toName := range binding.ToNames { new := &bindingTo{ binding: binding, toName: toName, } for _, old := range orderedBindings { if old.toName != toName { continue } old.ignored, new.ignored = resolveConflictBinding(ctx.frozenConfig, old.binding, new.binding) } orderedBindings = append(orderedBindings, new) } } if len(orderedBindings) == 0 { return &emptyStructEncoder{} } finalOrderedFields := []structFieldTo{} for _, bindingTo := range orderedBindings { if !bindingTo.ignored { finalOrderedFields = append(finalOrderedFields, structFieldTo{ encoder: bindingTo.binding.Encoder.(*structFieldEncoder), toName: bindingTo.toName, }) } } return &structEncoder{typ, finalOrderedFields} } func createCheckIsEmpty(ctx *ctx, typ reflect2.Type) checkIsEmpty { encoder := createEncoderOfNative(ctx, typ) if encoder != nil { return encoder } kind := typ.Kind() switch kind { case reflect.Interface: return &dynamicEncoder{typ} case reflect.Struct: return &structEncoder{typ: typ} case reflect.Array: return &arrayEncoder{} case reflect.Slice: return &sliceEncoder{} case reflect.Map: return encoderOfMap(ctx, typ) case reflect.Ptr: return &OptionalEncoder{} default: return &lazyErrorEncoder{err: fmt.Errorf("unsupported type: %v", typ)} } } func resolveConflictBinding(cfg *frozenConfig, old, new *Binding) (ignoreOld, ignoreNew bool) { newTagged := new.Field.Tag().Get(cfg.getTagKey()) != "" oldTagged := old.Field.Tag().Get(cfg.getTagKey()) != "" if newTagged { if oldTagged { if len(old.levels) > len(new.levels) { return true, false } else if len(new.levels) > len(old.levels) { return false, true } else { return true, true } } else { return true, false } } else { if oldTagged { return true, false } if len(old.levels) > len(new.levels) { return true, false } else if len(new.levels) > len(old.levels) { return false, true } else { return true, true } } } type structFieldEncoder struct { field reflect2.StructField fieldEncoder ValEncoder omitempty bool } func (encoder *structFieldEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { fieldPtr := encoder.field.UnsafeGet(ptr) encoder.fieldEncoder.Encode(fieldPtr, stream) if stream.Error != nil && stream.Error != io.EOF { stream.Error = fmt.Errorf("%s: %s", encoder.field.Name(), stream.Error.Error()) } } func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool { fieldPtr := encoder.field.UnsafeGet(ptr) return encoder.fieldEncoder.IsEmpty(fieldPtr) } func (encoder *structFieldEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool { isEmbeddedPtrNil, converted := encoder.fieldEncoder.(IsEmbeddedPtrNil) if !converted { return false } fieldPtr := encoder.field.UnsafeGet(ptr) return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr) } type IsEmbeddedPtrNil interface { IsEmbeddedPtrNil(ptr unsafe.Pointer) bool } type structEncoder struct { typ reflect2.Type fields []structFieldTo } type structFieldTo struct { encoder *structFieldEncoder toName string } func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { if !stream.incrementDepth() { return } isNotFirst := false for _, field := range encoder.fields { if field.encoder.omitempty && field.encoder.IsEmpty(ptr) { continue } if field.encoder.IsEmbeddedPtrNil(ptr) { continue } if isNotFirst { stream.WriteMore() } stream.WriteObjectField(field.toName) field.encoder.Encode(ptr, stream) isNotFirst = true } stream.WriteObjectEnd() if stream.Error != nil && stream.Error != io.EOF { stream.Error = fmt.Errorf("%v.%s", encoder.typ, stream.Error.Error()) } stream.decrementDepth() } func (encoder *structEncoder) IsEmpty(ptr unsafe.Pointer) bool { return false } type emptyStructEncoder struct { } func (encoder *emptyStructEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { stream.WriteEmptyObject() } func (encoder *emptyStructEncoder) IsEmpty(ptr unsafe.Pointer) bool { return false }