Supports decoding for null value

This commit is contained in:
Masaaki Goshima 2019-10-30 16:57:59 +09:00
parent 93521ac450
commit 4a90c16927
6 changed files with 83 additions and 6 deletions

View file

@ -228,6 +228,11 @@ func (d *Decoder) decodeValue(dst reflect.Value, src ast.Node) error {
if dst.IsNil() {
return nil
}
if src.Type() == ast.NullType {
// set nil value to pointer
dst.Set(reflect.Zero(valueType))
return nil
}
v := d.createDecodableValue(dst.Type())
if err := d.decodeValue(v, src); err != nil {
return errors.Wrapf(err, "failed to decode ptr value")
@ -399,6 +404,11 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
if !fieldValue.CanSet() {
return xerrors.Errorf("cannot set embedded type as unexported field %s.%s", field.PkgPath, field.Name)
}
if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType {
// set nil value to pointer
fieldValue.Set(reflect.Zero(fieldValue.Type()))
continue
}
newFieldValue := d.createDecodableValue(fieldValue.Type())
if err := d.decodeValue(newFieldValue, src); err != nil {
if xerrors.Is(err, errOverflowNumber) {
@ -416,6 +426,11 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
continue
}
fieldValue := structValue.Elem().FieldByName(field.Name)
if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType {
// set nil value to pointer
fieldValue.Set(reflect.Zero(fieldValue.Type()))
continue
}
newFieldValue := d.createDecodableValue(fieldValue.Type())
if err := d.decodeValue(newFieldValue, v); err != nil {
if xerrors.Is(err, errOverflowNumber) {
@ -461,6 +476,11 @@ func (d *Decoder) decodeSlice(dst reflect.Value, src ast.Node) error {
elemType := sliceType.Elem()
for iter.Next() {
v := iter.Value()
if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType {
// set nil value to pointer
sliceValue = reflect.Append(sliceValue, reflect.Zero(elemType))
continue
}
dstValue := d.createDecodableValue(elemType)
if err := d.decodeValue(dstValue, v); err != nil {
return errors.Wrapf(err, "failed to decode value")
@ -484,6 +504,15 @@ func (d *Decoder) decodeMap(dst reflect.Value, src ast.Node) error {
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
k := reflect.ValueOf(d.nodeToValue(key))
if k.IsValid() && k.Type().ConvertibleTo(keyType) {
k = k.Convert(keyType)
}
if valueType.Kind() == reflect.Ptr && value.Type() == ast.NullType {
// set nil value to pointer
mapValue.SetMapIndex(k, reflect.Zero(valueType))
continue
}
dstValue := d.createDecodableValue(valueType)
if err := d.decodeValue(dstValue, value); err != nil {
if xerrors.Is(err, errOverflowNumber) {
@ -492,8 +521,12 @@ func (d *Decoder) decodeMap(dst reflect.Value, src ast.Node) error {
}
return errors.Wrapf(err, "failed to decode value")
}
castedKey := reflect.ValueOf(d.nodeToValue(key)).Convert(keyType)
mapValue.SetMapIndex(castedKey, d.castToAssignableValue(dstValue, valueType))
if !k.IsValid() {
// expect nil key
mapValue.SetMapIndex(d.createDecodableValue(keyType), d.castToAssignableValue(dstValue, valueType))
continue
}
mapValue.SetMapIndex(k, d.castToAssignableValue(dstValue, valueType))
}
dst.Set(mapValue)
return nil

View file

@ -279,6 +279,25 @@ func TestDecoder(t *testing.T) {
"a: 50cent_of_dollar",
map[string]interface{}{"a": "50cent_of_dollar"},
},
// Nulls
{
"v:",
map[string]interface{}{"v": nil},
},
{
"v: ~",
map[string]interface{}{"v": nil},
},
{
"~: null key",
map[interface{}]string{nil: "null key"},
},
{
"v:",
map[string]*bool{"v": nil},
},
{
"v: .inf\n",
map[string]interface{}{"v": math.Inf(0)},

View file

@ -173,10 +173,16 @@ func (p *Parser) parseMappingValue(ctx *Context) (ast.Node, error) {
ctx.progress(1) // progress to mapping value token
tk := ctx.currentToken() // get mapping value token
ctx.progress(1) // progress to value token
value, err := p.parseToken(ctx, ctx.currentToken())
var value ast.Node
if vtk := ctx.currentToken(); vtk == nil {
value = ast.Null(token.New("null", "null", tk.Position))
} else {
v, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse mapping 'value' node")
}
value = v
}
keyColumn := key.GetToken().Position.Column
valueColumn := value.GetToken().Position.Column
if keyColumn == valueColumn {
@ -299,6 +305,9 @@ func (p *Parser) parseMapKey(tk *token.Token) ast.Node {
if tk.Type == token.MergeKeyType {
return ast.MergeKey(tk)
}
if tk.Type == token.NullType {
return ast.Null(tk)
}
return nil
}

View file

@ -66,6 +66,10 @@ func (c *Context) isEOS() bool {
return len(c.src)-1 <= c.idx
}
func (c *Context) isNextEOS() bool {
return len(c.src)-1 <= c.idx+1
}
func (c *Context) next() bool {
return c.idx < c.size
}

View file

@ -364,7 +364,7 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
return
case ':':
nc := ctx.nextChar()
if nc == ' ' || nc == '\n' {
if nc == ' ' || nc == '\n' || ctx.isNextEOS() {
// mapping value
tk := s.bufferedToken(ctx)
if tk != nil {

View file

@ -274,6 +274,8 @@ type ReservedKeyword string
const (
// Null `null` keyword
Null ReservedKeyword = "null"
// NullSymbol `~` keyword
NullSymbol = "~"
// False `false` keyword
False = "false"
// True `true` keyword
@ -299,6 +301,16 @@ var (
Position: pos,
}
},
NullSymbol: func(value, org string, pos *Position) *Token {
return &Token{
Type: NullType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
False: func(value string, org string, pos *Position) *Token {
return &Token{
Type: BoolType,