codec.go raw

   1  package javaproperties
   2  
   3  import (
   4  	"bytes"
   5  	"sort"
   6  	"strings"
   7  
   8  	"github.com/magiconair/properties"
   9  	"github.com/spf13/cast"
  10  )
  11  
  12  // Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
  13  type Codec struct {
  14  	KeyDelimiter string
  15  
  16  	// Store read properties on the object so that we can write back in order with comments.
  17  	// This will only be used if the configuration read is a properties file.
  18  	// TODO: drop this feature in v2
  19  	// TODO: make use of the global properties object optional
  20  	Properties *properties.Properties
  21  }
  22  
  23  func (c *Codec) Encode(v map[string]any) ([]byte, error) {
  24  	if c.Properties == nil {
  25  		c.Properties = properties.NewProperties()
  26  	}
  27  
  28  	flattened := map[string]any{}
  29  
  30  	flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
  31  
  32  	keys := make([]string, 0, len(flattened))
  33  
  34  	for key := range flattened {
  35  		keys = append(keys, key)
  36  	}
  37  
  38  	sort.Strings(keys)
  39  
  40  	for _, key := range keys {
  41  		_, _, err := c.Properties.Set(key, cast.ToString(flattened[key]))
  42  		if err != nil {
  43  			return nil, err
  44  		}
  45  	}
  46  
  47  	var buf bytes.Buffer
  48  
  49  	_, err := c.Properties.WriteComment(&buf, "#", properties.UTF8)
  50  	if err != nil {
  51  		return nil, err
  52  	}
  53  
  54  	return buf.Bytes(), nil
  55  }
  56  
  57  func (c *Codec) Decode(b []byte, v map[string]any) error {
  58  	var err error
  59  	c.Properties, err = properties.Load(b, properties.UTF8)
  60  	if err != nil {
  61  		return err
  62  	}
  63  
  64  	for _, key := range c.Properties.Keys() {
  65  		// ignore existence check: we know it's there
  66  		value, _ := c.Properties.Get(key)
  67  
  68  		// recursively build nested maps
  69  		path := strings.Split(key, c.keyDelimiter())
  70  		lastKey := strings.ToLower(path[len(path)-1])
  71  		deepestMap := deepSearch(v, path[0:len(path)-1])
  72  
  73  		// set innermost value
  74  		deepestMap[lastKey] = value
  75  	}
  76  
  77  	return nil
  78  }
  79  
  80  func (c Codec) keyDelimiter() string {
  81  	if c.KeyDelimiter == "" {
  82  		return "."
  83  	}
  84  
  85  	return c.KeyDelimiter
  86  }
  87