Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 58 additions & 52 deletions formam.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,65 +211,71 @@ func (dec Decoder) init() error {
// analyzePath analyzes the current path to walk through it.
// For example: users[0].name
func (dec *Decoder) analyzePath() (err error) {
inBracket := false
bracketClosed := false
traversedByBracket := false
nesting := 0
lastPos := 0
endPos := 0

// parse path
for i, char := range []byte(dec.path) {
if char == '[' && inBracket == false {
// found an opening bracket
bracketClosed = false
inBracket = true
dec.field = dec.path[lastPos:i]
lastPos = i + 1
continue
} else if inBracket {
// it is inside of bracket, so get its value
if char == ']' {
// found an closing bracket, so it will be recently close, so put as true the bracketClosed
// and put as false inBracket and pass the value of bracket to dec.key
inBracket = false
bracketClosed = true
if endPos == 0 { // foo[] without number.
dec.index = dec.path[lastPos:i]
} else {
dec.index = dec.path[lastPos:endPos]
}
lastPos = i + 1
// traverse the path
err = dec.traverse()
// flush the index already used by traverse
dec.index = ""
// check if the "traverse" failed
if err != nil {
return
}
} else {
// still inside the bracket, so to save the end position
endPos = i + 1
}
continue
} else if !inBracket {
// not found any bracket, so try found a field
if char == '.' {
// found a field, we need to know if the field is next of a closing bracket,
// for example: [0].Field
if bracketClosed {
bracketClosed = false
lastPos = i + 1
continue
}
// found a field, but is not next of a closing bracket, for example: Field1.Field2
switch char {
case '[':
// save current access field
if nesting == 0 {
traversedByBracket = false
dec.field = dec.path[lastPos:i]
//dec.field = tmp[:i]
lastPos = i + 1
if err = dec.traverse(); err != nil {
return
}
}
continue
nesting += 1

case ']':
// no matching open bracket - regular character
if nesting == 0 {
continue
}

// decrease nesting
nesting -= 1
// if still inside outer brackets - regular character
// for example: [nested[brackets]]
if nesting > 0 {
continue
}

traversedByBracket = true
dec.index = dec.path[lastPos:i]
lastPos = i + 1
// traverse the path
err = dec.traverse()
// flush the index already used by traverse
dec.index = ""
// check if the "traverse" failed
if err != nil {
return
}

case '.':
// inside brackets - regular character
// for example: [key.with.dots]
if nesting > 0 {
continue
}

// found a field, we need to know if the field is next to a closing bracket,
// if it is then no need to traverse again
// for example: [0].Field
if traversedByBracket {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to skip traversing if . is following ]
s[k]w == s[k].w (extra dot after closing bracket)

traversedByBracket = false
lastPos = i + 1
continue
}

// found a field, but is not next to a closing bracket,
// for example: Field1.Field2
dec.field = dec.path[lastPos:i]
lastPos = i + 1
if err = dec.traverse(); err != nil {
return
}
}
}

Expand Down
49 changes: 49 additions & 0 deletions formam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1125,3 +1125,52 @@ func TestMapToPtrStruct(t *testing.T) {
t.Errorf("The value in key \"key\" of M is incorrect: %q", v.ID)
}
}

func TestBracketsAndNestedPointers(t *testing.T) {
var s struct {
MapStringString map[string]string
MapStringPtrStruct map[string]struct {
ID string
}
MapStringMapStringString map[string]map[string]string
}

vals := url.Values{
"MapStringString[a[b][c]d]": []string{"MapStringString[a[b][c]d]"},
"MapStringString[name.with.dots]": []string{"MapStringString[name.with.dots]"},
"MapStringPtrStruct[k2]ID": []string{"MapStringPtrStruct[k2]ID"},
"MapStringMapStringString[a[b[c]d]]q]w": []string{"MapStringMapStringString[a[b[c]d]]q]w"},
}

dec := formam.NewDecoder(nil)

if err := dec.Decode(vals, &s); err != nil {
t.Fatalf("error when decode %s", err)
}

if v, ok := s.MapStringString["a[b][c]d"]; !ok {
t.Error("The key \"a[b][c]d\" in MapStringString does not exists")
} else if v != "MapStringString[a[b][c]d]" {
t.Error("The value in key \"a[b][c]d\" of MapStringString is incorrect")
}

if v, ok := s.MapStringString["name.with.dots"]; !ok {
t.Error("The key \"name.with.dots\" in MapStringString does not exists")
} else if v != "MapStringString[name.with.dots]" {
t.Error("The value in key \"name.with.dots\" of MapStringString is incorrect")
}

if v, ok := s.MapStringPtrStruct["k2"]; !ok {
t.Error("The key \"k2\" in MapStringPtrStruct does not exists")
} else if v.ID != "MapStringPtrStruct[k2]ID" {
t.Error("The value in key \"k2\" of MapStringPtrStruct is incorrect")
}

if v, ok := s.MapStringMapStringString["a[b[c]d]"]; !ok {
t.Error("The key \"a[b[c]d]\" in MapStringMapStringString does not exists")
} else if vv, ok := v["q]w"]; !ok {
t.Error("The key \"q]w\" in MapStringMapStringString[a[b[c]d]] does not exists")
} else if vv != "MapStringMapStringString[a[b[c]d]]q]w" {
t.Error("The value in key \"q]w\" of MapStringMapStringString[a[b[c]d]] is incorrect")
}
}