github.com/goki/ki@v1.1.11/kit/type.go (about)

     1  // Copyright (c) 2018, The GoKi Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package kit
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"encoding/xml"
    11  	"fmt"
    12  	"reflect"
    13  	"strings"
    14  )
    15  
    16  // Type provides JSON, XML marshal / unmarshal with encoding of underlying
    17  // type name using kit.Types type name registry
    18  type Type struct {
    19  	T reflect.Type
    20  }
    21  
    22  // ShortTypeName returns short package-qualified name of the type: package dir + "." + type name
    23  func (k Type) ShortTypeName() string {
    24  	return Types.TypeName(k.T)
    25  }
    26  
    27  // String satisfies the stringer interface
    28  func String(k Type) string {
    29  	if k.T == nil {
    30  		return "nil"
    31  	}
    32  	return k.ShortTypeName()
    33  }
    34  
    35  // MarshalJSON saves only the type name
    36  func (k Type) MarshalJSON() ([]byte, error) {
    37  	if k.T == nil {
    38  		b := []byte("null")
    39  		return b, nil
    40  	}
    41  	nm := "\"" + k.ShortTypeName() + "\""
    42  	b := []byte(nm)
    43  	return b, nil
    44  }
    45  
    46  // UnmarshalJSON loads the type name and looks it up in the Types registry of type names
    47  func (k *Type) UnmarshalJSON(b []byte) error {
    48  	if bytes.Equal(b, []byte("null")) {
    49  		k.T = nil
    50  		return nil
    51  	}
    52  	tn := string(bytes.Trim(bytes.TrimSpace(b), "\""))
    53  	// fmt.Printf("loading type: %v", tn)
    54  	typ := Types.Type(tn)
    55  	if typ == nil {
    56  		return fmt.Errorf("Type UnmarshalJSON: Types type name not found: %v", tn)
    57  	}
    58  	k.T = typ
    59  	return nil
    60  }
    61  
    62  // todo: try to save info as an attribute within a single element instead of
    63  // full start/end
    64  
    65  // MarshalXML saves only the type name
    66  func (k Type) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    67  	tokens := []xml.Token{start}
    68  	if k.T == nil {
    69  		tokens = append(tokens, xml.CharData("null"))
    70  	} else {
    71  		tokens = append(tokens, xml.CharData(k.ShortTypeName()))
    72  	}
    73  	tokens = append(tokens, xml.EndElement{start.Name})
    74  	for _, t := range tokens {
    75  		err := e.EncodeToken(t)
    76  		if err != nil {
    77  			return err
    78  		}
    79  	}
    80  	err := e.Flush()
    81  	if err != nil {
    82  		return err
    83  	}
    84  	return nil
    85  }
    86  
    87  // UnmarshalXML loads the type name and looks it up in the Types registry of type names
    88  func (k *Type) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    89  	t, err := d.Token()
    90  	if err != nil {
    91  		return err
    92  	}
    93  	ct, ok := t.(xml.CharData)
    94  	if ok {
    95  		tn := string(bytes.TrimSpace([]byte(ct)))
    96  		if tn == "null" {
    97  			k.T = nil
    98  		} else {
    99  			// fmt.Printf("loading type: %v\n", tn)
   100  			typ := Types.Type(tn)
   101  			if typ == nil {
   102  				return fmt.Errorf("Type UnmarshalXML: Types type name not found: %v", tn)
   103  			}
   104  			k.T = typ
   105  		}
   106  		t, err := d.Token()
   107  		if err != nil {
   108  			return err
   109  		}
   110  		et, ok := t.(xml.EndElement)
   111  		if ok {
   112  			if et.Name != start.Name {
   113  				return fmt.Errorf("Type UnmarshalXML: EndElement: %v does not match StartElement: %v", et.Name, start.Name)
   114  			}
   115  			return nil
   116  		}
   117  		return fmt.Errorf("Type UnmarshalXML: Token: %+v is not expected EndElement", et)
   118  	}
   119  	return fmt.Errorf("Type UnmarshalXML: Token: %+v is not expected EndElement", ct)
   120  }
   121  
   122  // StructTags returns a map[string]string of the tag string from a reflect.StructTag value
   123  // e.g., from StructField.Tag
   124  func StructTags(tags reflect.StructTag) map[string]string {
   125  	if len(tags) == 0 {
   126  		return nil
   127  	}
   128  	flds := strings.Fields(string(tags))
   129  	smap := make(map[string]string, len(flds))
   130  	for _, fld := range flds {
   131  		cli := strings.Index(fld, ":")
   132  		if cli < 0 || len(fld) < cli+3 {
   133  			continue
   134  		}
   135  		vl := strings.TrimSuffix(fld[cli+2:], `"`)
   136  		smap[fld[:cli]] = vl
   137  	}
   138  	return smap
   139  }
   140  
   141  // StringJSON returns a JSON representation of item, as a string
   142  // e.g., for printing / debugging etc.
   143  func StringJSON(it interface{}) string {
   144  	b, _ := json.MarshalIndent(it, "", "  ")
   145  	return string(b)
   146  }