Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cue: EncodeType doesn't unify embedded Go struct marked with a json inline tag with struct definition #1772

Open
yangzhares opened this issue Jun 18, 2022 · 3 comments
Labels

Comments

@yangzhares
Copy link

What version of CUE are you using (cue version)?

$ cue version
cue version v0.4.3 darwin/amd64

$ go version
go version go1.17.6 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What did you do?

When did convert a Go type to CUE value via EncodeType, embedded Go struct marked with a json inline tag doesn't unify with struct definition, an empty CUE field will map to CUE value of embedded Go struct.

Reproduce this via below code:

package main

import (
	"fmt"

	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/format"
)

func main() {
	ctx := cuecontext.New()
	val := ctx.EncodeType(&Spec{})
	node := val.Syntax()
	raw, _ := format.Node(node)
	fmt.Println(string(raw))
}

type Spec struct {
	Example Example `json:"example"`
}

type Example struct {
	Hello `json:",inline"`
	World `json:",inline"`
}

type Hello struct {
	Hello string `json:"hello"`
}

type World struct {
	World string `json:"world"`
}

What did you expect to see?

{
	example: {
		hello: string
		world: string
	}
}

What did you see instead?

{
	example: {
		"": {
			hello: string
			world: string
		}
	}
}
@yangzhares yangzhares added NeedsInvestigation Triage Requires triage/attention labels Jun 18, 2022
@rogpeppe
Copy link
Member

rogpeppe commented Jun 24, 2022

Nice find! This does indeed look like a bug with the Go-to-CUE type conversion logic.
Thanks for the nice small reproducer too.
Here's some slightly simpler code that reproduces the issue and also verifies that the
Go type does encode correctly:

package main

import (
	"encoding/json"
	"fmt"

	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/format"
)

func main() {
	ctx := cuecontext.New()
	val := ctx.EncodeType(Example{})
	node := val.Syntax()
	raw, _ := format.Node(node)
	fmt.Printf("-- gotype.cue --\n%s\n", raw)
	data, _ := json.Marshal(Example{
		Hello: Hello{"one"},
	})
	fmt.Printf("-- gotype.json --\n%s\n", data)
}

type Example struct {
	Hello
}

type Hello struct {
	Hello string `json:"hello"`
}

This prints the following on my machine (with CUE v0.4.3):

-- gotype.cue --
{
	"": {
		hello: string
	}
}
-- gotype.json --
{"hello":"one"}

@rogpeppe rogpeppe added NeedsFix and removed NeedsInvestigation Triage Requires triage/attention labels Jun 24, 2022
yangzhares pushed a commit to yangzhares/cue that referenced this issue Jun 25, 2022
…truct definition

Signed-off-by: Clare Yang (zhanyang) <zhanyang@cisco.com>
yangzhares pushed a commit to yangzhares/cue that referenced this issue Jun 28, 2022
…nition

This actually fixes a bug in GoTypeToExpr under internal/core/convert, it
doesn't unify embedded Go struct with struct definition, and add two test
cases to verify this fix working well.

Fixes: cue-lang#1772

Signed-off-by: Clare Yang (zhanyang) <zhanyang@cisco.com>
@myitcv myitcv added the zGarden label Jun 13, 2023
@jlarfors
Copy link

jlarfors commented Nov 6, 2023

I ran into this today, using structs from a 3rd party package. My workaround was to create local versions of those structs, with the added json tags.

Are there any updates on the issue? I'm working on something that could expose this to end users, which would be a potential stoppper...

@jlarfors
Copy link

jlarfors commented Nov 7, 2023

After more testing I realised the 3rd party structs I was using had nested embedding on quite some levels... Hence I wrote a hacky little function to unify embedded structs with it's parent. This might be helpful for others coming here who want a hacky fix:

func cueRemoveEmbeddedAnomaly(val cue.Value) (cue.Value, error) {
	iter, err := val.Fields(cue.All())
	if err != nil {
		return val, err
	}
	newVal := val.Context().CompileString("{}")
	for iter.Next() {
		field := iter.Value()
		fieldPath := cue.MakePath(iter.Selector())
		label, ok := field.Label()
		if !ok {
			continue
		}

		if field.Kind() != cue.StructKind {
			newVal = newVal.FillPath(fieldPath, field)
			continue
		}

		child, err := cueRemoveEmbeddedAnomaly(field)
		if err != nil {
			return val, err
		}
		if label == "" {
			newVal = newVal.Unify(child)
			continue
		}
		newVal = newVal.FillPath(fieldPath, child)
	}

	return newVal, nil
}

Now if you run your test like this, it should give the desired output:

func TestEncodeTypeWithEmbeddedHack(t *testing.T) {
	type Hello struct {
		Hello string `json:"hello"`
	}
	type Example struct {
		Hello
	}

	ctx := cuecontext.New()
	val := ctx.EncodeType(Example{})
	val, err := cueRemoveEmbeddedAnomaly(val)
	if err != nil {
		t.Fatal(err)
	}
	node := val.Syntax()
	raw, _ := format.Node(node)
	fmt.Printf("-- gotype.cue --\n%s\n", raw)
	data, _ := json.Marshal(Example{
		Hello: Hello{"one"},
	})
	fmt.Printf("-- gotype.json --\n%s\n", data)
}

@mvdan mvdan removed the zGarden label Feb 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants