Repository: openconfig/ygot
Pull request #645 (2022-04-14 09:29:59)

Presence Containers
This commit adds support for YANG presence containers. It adds a tag to
the field of generated Go Struct. When rendering JSON documents, before
deleting/omitting empty containers, it checks for this struct field tag.

As per the OpenConfig style guide, YANG presence containers are not
used, as such this would only be used with 3rd party YANG models. As
such a CLI knob is added to generator.go to enable this behaviour with
-yangpresence=true

google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

For more information, open the [CLA check for this pull request](https://github.com/openconfig/ygot/pull/645/checks?check_run_id=6021662887).
coveralls: 
[![Coverage Status](https://coveralls.io/builds/48281384/badge)](https://coveralls.io/builds/48281384)

Coverage increased (+0.007%) to 90.844% when pulling **ca3fce2d1a3b7712519d5d9c3bc3e991ff1d5dcd on hansthienpondt:presence-containers-issue329** into **c15884d06738b435020d84e167f7143627bf19ea on openconfig:master**.

hansthienpondt: proposed fix for https://github.com/openconfig/ygot/issues/329
wenovus: Sorry, another comment is we should mark this flag as "only tested for marshalling JSON". So note to the user that unmarshalling doesn't yet work (at least not until we add tests)
hansthienpondt: > Sorry, another comment is we should mark this flag as "only tested for marshalling JSON". So note to the user that unmarshalling doesn't yet work (at least not until we add tests)

Could you point me to the test cases for Unmarshaling JSON? I've only come across https://github.com/openconfig/ygot/blob/c15884d06738b435020d84e167f7143627bf19ea/ytypes/unmarshal_test.go#L23
Is this where a test should be defined?

In the Unmarshaling case, I'd expect this (an empty value) to be a use/test-case anyhow right? Whether this is a presence container or not, is not known yet at that point?
PS: the testing I've done so far, it looks like (un)marshalling works fine for presence containers, but obviously a test provides 100% guarantee
wenovus: There is also [TestUnmarshalContainer](https://github.com/openconfig/ygot/search?q=TestUnmarshalContainer), the tests are split by node type. That sounds good, if there is no extra implementation involved sounds good to me to just add the unmarshal tests in this PR and then we can say for this flag that "it's supported for JSON marshalling and unmarshalling". In particular this doesn't work with the `populate_defaults` flag, but as long as we mention that it's only supported for these tested operations this sounds good to me to add.
hansthienpondt: > There is also [TestUnmarshalContainer](https://github.com/openconfig/ygot/search?q=TestUnmarshalContainer), the tests are split by node type. That sounds good, if there is no extra implementation involved sounds good to me to just add the unmarshal tests in this PR and then we can say for this flag that "it's supported for JSON marshalling and unmarshalling". In particular this doesn't work with the `populate_defaults` flag, but as long as we mention that it's only supported for these tested operations this sounds good to me to add.

Done, could you check whether the test's I've added cover enough ground?
hansthienpondt: > ```go
> 			if diff := pretty.Compare(gotietf, tt.wantIETF); diff != "" {
> 				t.Errorf("ConstructIETFJSON(%v): did not get expected output, diff(-got,+want):\n%v", tt.in, diff)
> 			}
> ```
> 
> For this block in render_test.go I'm wondering if we should convert to using `cmp.Diff`, which is the standard comparison tool which by default differentiates between the empty type and a nil value for comparing `map[string]interface{}`.

We could, but existing test-cases would break (not functionally though):
```
--- FAIL: TestConstructJSON (0.01s)
    --- FAIL: TestConstructJSON/multi-element_render_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/json_test_with_complex_children_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/json_test_with_complex_children_with_PrependModuleNameIdentityref=true_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/device_example_#1_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/device_example_#2_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/union_example_-_string_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/binary_example_ConstructIETFJSON (0.00s)
    --- FAIL: TestConstructJSON/uncompressed_device_example_with_presence_containers_with_data_in_tree_ConstructIETFJSON (0.00s)
```
for example:
```
did not get expected output, diff(-got,+want):
        -               "updates":      []interface{}{string("AQID"), string("AQIDBA==")},
        +               "updates":      []string{"AQID", "AQIDBA=="},

        -                                               "peer-as":          uint32(5413),
        +                                               "peer-as":          int(5413),

        -                               "as":        uint32(15169),
        +                               "as":        int(15169),

```

I have staged the changes of the test-cases, so if It would be your preference to do the conversion in this PR, I'll push it!

Patch:
diff --git a/generator/generator.go b/generator/generator.go
index e5e3cf2..3dda203 100644
--- a/generator/generator.go
+++ b/generator/generator.go
@@ -86,6 +86,7 @@ var (
 	generateRename          = flag.Bool("generate_rename", false, "If set to true, rename methods are generated for lists within the Go code.")
 	addAnnotations          = flag.Bool("annotations", false, "If set to true, metadata annotations are added within the generated structs.")
 	annotationPrefix        = flag.String("annotation_prefix", ygen.DefaultAnnotationPrefix, "String to be appended to each metadata field within the generated structs if annoations is set to true.")
+	addYangPresence         = flag.Bool("yangpresence", false, "If set to true, a tag will be added to the field of a generated Go struct to indicate when a YANG presence container is being used.")
 	generateAppend          = flag.Bool("generate_append", false, "If set to true, append methods are generated for YANG lists (Go maps) within the Go code.")
 	generateGetters         = flag.Bool("generate_getters", false, "If set to true, getter methdos that retrieve or create an element are generated for YANG container (Go struct pointer) or list (Go map) fields within the generated code.")
 	generateDelete          = flag.Bool("generate_delete", false, "If set to true, delete methods are generated for YANG lists (Go maps) within the Go code.")
@@ -349,6 +350,7 @@ func main() {
 				GenerateRenameMethod:                *generateRename,
 				AddAnnotationFields:                 *addAnnotations,
 				AnnotationPrefix:                    *annotationPrefix,
+				AddYangPresence:                     *addYangPresence,
 				GenerateGetters:                     *generateGetters,
 				GenerateDeleteMethod:                *generateDelete,
 				GenerateAppendMethod:                *generateAppend,
diff --git a/util/yang.go b/util/yang.go
index 0d45eef..cd44a52 100644
--- a/util/yang.go
+++ b/util/yang.go
@@ -188,6 +188,12 @@ func IsYgotAnnotation(s reflect.StructField) bool {
 	return ok
 }
 
+// IsYangPresence reports whether struct field s is a YANG presence container.
+func IsYangPresence(s reflect.StructField) bool {
+	_, ok := s.Tag.Lookup("yangPresence")
+	return ok
+}
+
 // IsSimpleEnumerationType returns true when the type supplied is a simple
 // enumeration (i.e., a leaf that is defined as type enumeration { ... },
 // and is not a typedef that contains an enumeration, or a union that
diff --git a/ygen/codegen.go b/ygen/codegen.go
index 03a4fef..f6d014b 100644
--- a/ygen/codegen.go
+++ b/ygen/codegen.go
@@ -191,6 +191,13 @@ type GoOpts struct {
 	// AnnotationPrefix specifies the string which is prefixed to the name of
 	// annotation fields. It defaults to Λ.
 	AnnotationPrefix string
+	// AddYangPresence specifies whether tags should be added to the generated
+	// fields of a struct. When set to true, a struct tag will be added to the field
+	// when a YANG container is a presence container
+	// https://datatracker.ietf.org/doc/html/rfc6020#section-7.5.1
+	// a field tag of `yangPresence="true"` will only be added if the container is
+	// a YANG presence container, and will be omitted if this is not the case.
+	AddYangPresence bool
 	// GenerateGetters specifies whether GetOrCreate* methods should be created
 	// for struct pointer (YANG container) and map (YANG list) fields of generated
 	// structs.
diff --git a/ygen/gogen.go b/ygen/gogen.go
index f1df307..5c56430 100644
--- a/ygen/gogen.go
+++ b/ygen/gogen.go
@@ -1699,6 +1699,15 @@ func writeGoStruct(targetStruct *Directory, goStructElements map[string]*Directo
 
 		metadataTagBuf.WriteString(` ygotAnnotation:"true"`)
 
+		if goOpts.AddYangPresence {
+			// TODO(wenovus):
+			// a presence container is an unimplemented keyword in goyang.
+			// if and when this changes, the field lookup below would need to change as well.
+			if field.IsContainer() && len(field.Extra["presence"]) > 0 {
+				tagBuf.WriteString(` yangPresence:"true"`)
+			}
+		}
+
 		fieldDef.Tags = tagBuf.String()
 
 		// Append the generated field definition to the set of fields of the struct.
diff --git a/ygot/render.go b/ygot/render.go
index 3225131..9f516ad 100644
--- a/ygot/render.go
+++ b/ygot/render.go
@@ -1156,7 +1156,7 @@ func structJSON(s GoStruct, parentMod string, args jsonOutputConfig) (map[string
 			continue
 		}
 
-		if mp, ok := value.(map[string]interface{}); ok && len(mp) == 0 {
+		if mp, ok := value.(map[string]interface{}); ok && len(mp) == 0 && !util.IsYangPresence(fType) {
 			continue
 		}
 


Test patch:
diff --git a/testdata/modules/presence-container-example.yang b/testdata/modules/presence-container-example.yang
new file mode 100644
index 0000000..57732bf
--- /dev/null
+++ b/testdata/modules/presence-container-example.yang
@@ -0,0 +1,38 @@
+module presence-container-example {
+  prefix "pc";
+  namespace "urn:pc";
+  description
+  "A simple test module with a YANG presence container";
+
+  grouping parent-config {
+    leaf one { type string; }
+    leaf three {
+      type enumeration {
+        enum ONE;
+        enum TWO;
+      }
+    }
+    leaf four {
+      type binary;
+    }
+  }
+
+  container parent {
+    description
+      "I am a parent container
+       that has 4 children.";
+    container child {
+      presence "This is an example presence container";
+      container config {
+        uses parent-config;
+      }
+      container state {
+        config false;
+        uses parent-config;
+        leaf two { type string; }
+      }
+    }
+  }
+}
+
+
diff --git a/util/yang_test.go b/util/yang_test.go
index 6378dcc..21ba64c 100644
--- a/util/yang_test.go
+++ b/util/yang_test.go
@@ -1019,6 +1019,41 @@ func TestIsYgotAnnotation(t *testing.T) {
 	}
 }
 
+func TestIsYangPresence(t *testing.T) {
+	type testStruct struct {
+		Yes *string `yangPresence:"true"`
+		No  *string
+	}
+	structFieldYes, ok := reflect.TypeOf(testStruct{}).FieldByName("Yes")
+	if !ok {
+		t.Fatalf("Cannot find field Yes in testStruct")
+	}
+
+	structFieldNo, ok := reflect.TypeOf(testStruct{}).FieldByName("No")
+	if !ok {
+		t.Fatalf("Cannot find field No in testStruct")
+	}
+	tests := []struct {
+		name string
+		in   reflect.StructField
+		want bool
+	}{{
+		name: "yangPresence container/field",
+		in:   structFieldYes,
+		want: true,
+	}, {
+		name: "standard field",
+		in:   structFieldNo,
+		want: false,
+	}}
+
+	for _, tt := range tests {
+		if got := IsYangPresence(tt.in); got != tt.want {
+			t.Errorf("%s: IsYangPresence(%#v): did not get expected result, got: %v, want: %v", tt.name, tt.in, got, tt.want)
+		}
+	}
+}
+
 // complexUnionTypeName is the name used to refer to the name of the union
 // type containing the slice of input types to the functions.
 const complexUnionTypeName = "complexUnionTypeName"
diff --git a/ygen/codegen_test.go b/ygen/codegen_test.go
index cf4dc3c..2891d3b 100644
--- a/ygen/codegen_test.go
+++ b/ygen/codegen_test.go
@@ -1171,6 +1171,22 @@ func TestSimpleStructs(t *testing.T) {
 			},
 		},
 		wantErrSubstring: "has a union key containing a binary",
+	}, {
+		name:    "module with presence containers",
+		inFiles: []string{filepath.Join(datapath, "presence-container-example.yang")},
+		inConfig: GeneratorConfig{
+			TransformationOptions: TransformationOpts{
+				GenerateFakeRoot: true,
+				FakeRootName:     "device",
+			},
+			GoOptions: GoOpts{
+				GenerateSimpleUnions:    true,
+				GenerateLeafGetters:     true,
+				GeneratePopulateDefault: true,
+				AddYangPresence:         true,
+			},
+		},
+		wantStructsCodeFile: filepath.Join(TestRoot, "testdata/structs/presence-container-example.formatted-txt"),
 	}}
 
 	for _, tt := range tests {
diff --git a/ygen/gogen_test.go b/ygen/gogen_test.go
index 2ea1710..61d68f9 100644
--- a/ygen/gogen_test.go
+++ b/ygen/gogen_test.go
@@ -536,6 +536,135 @@ func (t *InputStruct) Validate(opts ...ygot.ValidationOption) error {
 // that are included in the generated code.
 func (t *InputStruct) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
 
+// ΛBelongingModule returns the name of the module in whose namespace
+// InputStruct belongs.
+func (*InputStruct) ΛBelongingModule() string {
+	return "exmod"
+}
+`,
+		},
+	}, {
+		name: "nested container in struct with presence container",
+		inStructToMap: &Directory{
+			Name: "InputStruct",
+			Entry: &yang.Entry{
+				Name: "input-struct",
+				Parent: &yang.Entry{
+					Name: "root-module",
+					Node: &yang.Module{
+						Name: "exmod",
+						Namespace: &yang.Value{
+							Name: "u:exmod",
+						},
+						Modules: modules,
+					},
+				},
+				Node: &yang.Module{
+					Name: "exmod",
+					Namespace: &yang.Value{
+						Name: "u:exmod",
+					},
+					Modules: modules,
+				},
+				Extra: map[string][]interface{}{
+					"presence": {&yang.Value{Name: "presence c1"}},
+				},
+			},
+			Fields: map[string]*yang.Entry{
+				"c1": {
+					Name: "c1",
+					Dir:  map[string]*yang.Entry{},
+					Kind: yang.DirectoryEntry,
+					Parent: &yang.Entry{
+						Name: "input-struct",
+						Parent: &yang.Entry{
+							Name: "root-module",
+							Node: &yang.Module{
+								Name: "exmod",
+								Namespace: &yang.Value{
+									Name: "u:exmod",
+								},
+								Modules: modules,
+							},
+						},
+					},
+					Node: &yang.Leaf{
+						Parent: &yang.Module{
+							Name: "exmod",
+							Namespace: &yang.Value{
+								Name: "u:exmod",
+							},
+							Modules: modules,
+						},
+					},
+					Extra: map[string][]interface{}{
+						"presence": {&yang.Value{Name: "presence c1"}},
+					},
+				},
+			},
+			Path: []string{"", "root-module", "input-struct"},
+		},
+		inGoOpts: GoOpts{
+			AddYangPresence: true,
+		},
+		inUniqueDirectoryNames: map[string]string{"/root-module/input-struct/c1": "InputStruct_C1"},
+		wantCompressed: wantGoStructOut{
+			structs: `
+// InputStruct represents the /root-module/input-struct YANG schema element.
+type InputStruct struct {
+	C1	*InputStruct_C1	` + "`" + `path:"c1" module:"exmod" yangPresence:"true"` + "`" + `
+}
+
+// IsYANGGoStruct ensures that InputStruct implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*InputStruct) IsYANGGoStruct() {}
+`,
+			methods: `
+// Validate validates s against the YANG schema corresponding to its type.
+func (t *InputStruct) Validate(opts ...ygot.ValidationOption) error {
+	if err := ytypes.Validate(SchemaTree["InputStruct"], t, opts...); err != nil {
+		return err
+	}
+	return nil
+}
+
+// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
+// that are included in the generated code.
+func (t *InputStruct) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
+
+// ΛBelongingModule returns the name of the module in whose namespace
+// InputStruct belongs.
+func (*InputStruct) ΛBelongingModule() string {
+	return "exmod"
+}
+`,
+		},
+		wantUncompressed: wantGoStructOut{
+			structs: `
+// InputStruct represents the /root-module/input-struct YANG schema element.
+type InputStruct struct {
+	C1	*InputStruct_C1	` + "`" + `path:"c1" module:"exmod" yangPresence:"true"` + "`" + `
+}
+
+// IsYANGGoStruct ensures that InputStruct implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*InputStruct) IsYANGGoStruct() {}
+`,
+			methods: `
+// Validate validates s against the YANG schema corresponding to its type.
+func (t *InputStruct) Validate(opts ...ygot.ValidationOption) error {
+	if err := ytypes.Validate(SchemaTree["InputStruct"], t, opts...); err != nil {
+		return err
+	}
+	return nil
+}
+
+// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
+// that are included in the generated code.
+func (t *InputStruct) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
+
 // ΛBelongingModule returns the name of the module in whose namespace
 // InputStruct belongs.
 func (*InputStruct) ΛBelongingModule() string {
diff --git a/ygen/testdata/structs/presence-container-example.formatted-txt b/ygen/testdata/structs/presence-container-example.formatted-txt
new file mode 100644
index 0000000..a036a3a
--- /dev/null
+++ b/ygen/testdata/structs/presence-container-example.formatted-txt
@@ -0,0 +1,363 @@
+/*
+Package ocstructs is a generated package which contains definitions
+of structs which represent a YANG schema. The generated schema can be
+compressed by a series of transformations (compression was false
+in this case).
+
+This package was generated by codegen-tests
+using the following YANG input files:
+	- ../testdata/modules/presence-container-example.yang
+Imported modules were sourced from:
+*/
+package ocstructs
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+
+	"github.com/openconfig/ygot/ygot"
+)
+
+// Binary is a type that is used for fields that have a YANG type of
+// binary. It is used such that binary fields can be distinguished from
+// leaf-lists of uint8s (which are mapped to []uint8, equivalent to
+// []byte in reflection).
+type Binary []byte
+
+// YANGEmpty is a type that is used for fields that have a YANG type of
+// empty. It is used such that empty fields can be distinguished from boolean fields
+// in the generated code.
+type YANGEmpty bool
+
+// UnionInt8 is an int8 type assignable to unions of which it is a subtype.
+type UnionInt8 int8
+
+// UnionInt16 is an int16 type assignable to unions of which it is a subtype.
+type UnionInt16 int16
+
+// UnionInt32 is an int32 type assignable to unions of which it is a subtype.
+type UnionInt32 int32
+
+// UnionInt64 is an int64 type assignable to unions of which it is a subtype.
+type UnionInt64 int64
+
+// UnionUint8 is a uint8 type assignable to unions of which it is a subtype.
+type UnionUint8 uint8
+
+// UnionUint16 is a uint16 type assignable to unions of which it is a subtype.
+type UnionUint16 uint16
+
+// UnionUint32 is a uint32 type assignable to unions of which it is a subtype.
+type UnionUint32 uint32
+
+// UnionUint64 is a uint64 type assignable to unions of which it is a subtype.
+type UnionUint64 uint64
+
+// UnionFloat64 is a float64 type assignable to unions of which it is a subtype.
+type UnionFloat64 float64
+
+// UnionString is a string type assignable to unions of which it is a subtype.
+type UnionString string
+
+// UnionBool is a bool type assignable to unions of which it is a subtype.
+type UnionBool bool
+
+// UnionUnsupported is an interface{} wrapper type for unsupported types. It is
+// assignable to unions of which it is a subtype.
+type UnionUnsupported struct {
+	Value interface{}
+}
+
+// Device represents the /device YANG schema element.
+type Device struct {
+	Parent	*PresenceContainerExample_Parent	`path:"parent" module:"presence-container-example"`
+}
+
+// IsYANGGoStruct ensures that Device implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*Device) IsYANGGoStruct() {}
+
+// PopulateDefaults recursively populates unset leaf fields in the Device
+// with default values as specified in the YANG schema, instantiating any nil
+// container fields.
+func (t *Device) PopulateDefaults() {
+	if (t == nil) {
+		return
+	}
+	ygot.BuildEmptyTree(t)
+	t.Parent.PopulateDefaults()
+}
+
+// ΛBelongingModule returns the name of the module in whose namespace
+// Device belongs.
+func (*Device) ΛBelongingModule() string {
+	return ""
+}
+
+// PresenceContainerExample_Parent represents the /presence-container-example/parent YANG schema element.
+type PresenceContainerExample_Parent struct {
+	Child	*PresenceContainerExample_Parent_Child	`path:"child" module:"presence-container-example" yangPresence:"true"`
+}
+
+// IsYANGGoStruct ensures that PresenceContainerExample_Parent implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*PresenceContainerExample_Parent) IsYANGGoStruct() {}
+
+// PopulateDefaults recursively populates unset leaf fields in the PresenceContainerExample_Parent
+// with default values as specified in the YANG schema, instantiating any nil
+// container fields.
+func (t *PresenceContainerExample_Parent) PopulateDefaults() {
+	if (t == nil) {
+		return
+	}
+	ygot.BuildEmptyTree(t)
+	t.Child.PopulateDefaults()
+}
+
+// ΛBelongingModule returns the name of the module in whose namespace
+// PresenceContainerExample_Parent belongs.
+func (*PresenceContainerExample_Parent) ΛBelongingModule() string {
+	return "presence-container-example"
+}
+
+// PresenceContainerExample_Parent_Child represents the /presence-container-example/parent/child YANG schema element.
+type PresenceContainerExample_Parent_Child struct {
+	Config	*PresenceContainerExample_Parent_Child_Config	`path:"config" module:"presence-container-example"`
+	State	*PresenceContainerExample_Parent_Child_State	`path:"state" module:"presence-container-example"`
+}
+
+// IsYANGGoStruct ensures that PresenceContainerExample_Parent_Child implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*PresenceContainerExample_Parent_Child) IsYANGGoStruct() {}
+
+// PopulateDefaults recursively populates unset leaf fields in the PresenceContainerExample_Parent_Child
+// with default values as specified in the YANG schema, instantiating any nil
+// container fields.
+func (t *PresenceContainerExample_Parent_Child) PopulateDefaults() {
+	if (t == nil) {
+		return
+	}
+	ygot.BuildEmptyTree(t)
+	t.Config.PopulateDefaults()
+	t.State.PopulateDefaults()
+}
+
+// ΛBelongingModule returns the name of the module in whose namespace
+// PresenceContainerExample_Parent_Child belongs.
+func (*PresenceContainerExample_Parent_Child) ΛBelongingModule() string {
+	return "presence-container-example"
+}
+
+// PresenceContainerExample_Parent_Child_Config represents the /presence-container-example/parent/child/config YANG schema element.
+type PresenceContainerExample_Parent_Child_Config struct {
+	Four	Binary	`path:"four" module:"presence-container-example"`
+	One	*string	`path:"one" module:"presence-container-example"`
+	Three	E_PresenceContainerExample_Parent_Child_Config_Three	`path:"three" module:"presence-container-example"`
+}
+
+// IsYANGGoStruct ensures that PresenceContainerExample_Parent_Child_Config implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*PresenceContainerExample_Parent_Child_Config) IsYANGGoStruct() {}
+
+// GetFour retrieves the value of the leaf Four from the PresenceContainerExample_Parent_Child_Config
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if Four is set, it can
+// safely use t.GetFour() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.Four == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_Config) GetFour() Binary {
+	if t == nil || t.Four ==  nil {
+		return nil
+	}
+	return t.Four
+}
+
+// GetOne retrieves the value of the leaf One from the PresenceContainerExample_Parent_Child_Config
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if One is set, it can
+// safely use t.GetOne() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.One == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_Config) GetOne() string {
+	if t == nil || t.One == nil {
+		return ""
+	}
+	return *t.One
+}
+
+// GetThree retrieves the value of the leaf Three from the PresenceContainerExample_Parent_Child_Config
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if Three is set, it can
+// safely use t.GetThree() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.Three == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_Config) GetThree() E_PresenceContainerExample_Parent_Child_Config_Three {
+	if t == nil || t.Three ==  0 {
+		return 0
+	}
+	return t.Three
+}
+
+// PopulateDefaults recursively populates unset leaf fields in the PresenceContainerExample_Parent_Child_Config
+// with default values as specified in the YANG schema, instantiating any nil
+// container fields.
+func (t *PresenceContainerExample_Parent_Child_Config) PopulateDefaults() {
+	if (t == nil) {
+		return
+	}
+	ygot.BuildEmptyTree(t)
+}
+
+// ΛBelongingModule returns the name of the module in whose namespace
+// PresenceContainerExample_Parent_Child_Config belongs.
+func (*PresenceContainerExample_Parent_Child_Config) ΛBelongingModule() string {
+	return "presence-container-example"
+}
+
+// PresenceContainerExample_Parent_Child_State represents the /presence-container-example/parent/child/state YANG schema element.
+type PresenceContainerExample_Parent_Child_State struct {
+	Four	Binary	`path:"four" module:"presence-container-example"`
+	One	*string	`path:"one" module:"presence-container-example"`
+	Three	E_PresenceContainerExample_Parent_Child_Config_Three	`path:"three" module:"presence-container-example"`
+	Two	*string	`path:"two" module:"presence-container-example"`
+}
+
+// IsYANGGoStruct ensures that PresenceContainerExample_Parent_Child_State implements the yang.GoStruct
+// interface. This allows functions that need to handle this struct to
+// identify it as being generated by ygen.
+func (*PresenceContainerExample_Parent_Child_State) IsYANGGoStruct() {}
+
+// GetFour retrieves the value of the leaf Four from the PresenceContainerExample_Parent_Child_State
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if Four is set, it can
+// safely use t.GetFour() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.Four == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_State) GetFour() Binary {
+	if t == nil || t.Four ==  nil {
+		return nil
+	}
+	return t.Four
+}
+
+// GetOne retrieves the value of the leaf One from the PresenceContainerExample_Parent_Child_State
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if One is set, it can
+// safely use t.GetOne() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.One == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_State) GetOne() string {
+	if t == nil || t.One == nil {
+		return ""
+	}
+	return *t.One
+}
+
+// GetThree retrieves the value of the leaf Three from the PresenceContainerExample_Parent_Child_State
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if Three is set, it can
+// safely use t.GetThree() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.Three == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_State) GetThree() E_PresenceContainerExample_Parent_Child_Config_Three {
+	if t == nil || t.Three ==  0 {
+		return 0
+	}
+	return t.Three
+}
+
+// GetTwo retrieves the value of the leaf Two from the PresenceContainerExample_Parent_Child_State
+// struct. If the field is unset but has a default value in the YANG schema,
+// then the default value will be returned.
+// Caution should be exercised whilst using this method since when without a
+// default value, it will return the Go zero value if the field is explicitly
+// unset. If the caller explicitly does not care if Two is set, it can
+// safely use t.GetTwo() to retrieve the value. In the case that the
+// caller has different actions based on whether the leaf is set or unset, it
+// should use 'if t.Two == nil' before retrieving the leaf's value.
+func (t *PresenceContainerExample_Parent_Child_State) GetTwo() string {
+	if t == nil || t.Two == nil {
+		return ""
+	}
+	return *t.Two
+}
+
+// PopulateDefaults recursively populates unset leaf fields in the PresenceContainerExample_Parent_Child_State
+// with default values as specified in the YANG schema, instantiating any nil
+// container fields.
+func (t *PresenceContainerExample_Parent_Child_State) PopulateDefaults() {
+	if (t == nil) {
+		return
+	}
+	ygot.BuildEmptyTree(t)
+}
+
+// ΛBelongingModule returns the name of the module in whose namespace
+// PresenceContainerExample_Parent_Child_State belongs.
+func (*PresenceContainerExample_Parent_Child_State) ΛBelongingModule() string {
+	return "presence-container-example"
+}
+
+// E_PresenceContainerExample_Parent_Child_Config_Three is a derived int64 type which is used to represent
+// the enumerated node PresenceContainerExample_Parent_Child_Config_Three. An additional value named
+// PresenceContainerExample_Parent_Child_Config_Three_UNSET is added to the enumeration which is used as
+// the nil value, indicating that the enumeration was not explicitly set by
+// the program importing the generated structures.
+type E_PresenceContainerExample_Parent_Child_Config_Three int64
+
+// IsYANGGoEnum ensures that PresenceContainerExample_Parent_Child_Config_Three implements the yang.GoEnum
+// interface. This ensures that PresenceContainerExample_Parent_Child_Config_Three can be identified as a
+// mapped type for a YANG enumeration.
+func (E_PresenceContainerExample_Parent_Child_Config_Three) IsYANGGoEnum() {}
+
+// ΛMap returns the value lookup map associated with  PresenceContainerExample_Parent_Child_Config_Three.
+func (E_PresenceContainerExample_Parent_Child_Config_Three) ΛMap() map[string]map[int64]ygot.EnumDefinition { return ΛEnum; }
+
+// String returns a logging-friendly string for E_PresenceContainerExample_Parent_Child_Config_Three.
+func (e E_PresenceContainerExample_Parent_Child_Config_Three) String() string {
+	return ygot.EnumLogString(e, int64(e), "E_PresenceContainerExample_Parent_Child_Config_Three")
+}
+
+const (
+	// PresenceContainerExample_Parent_Child_Config_Three_UNSET corresponds to the value UNSET of PresenceContainerExample_Parent_Child_Config_Three
+	PresenceContainerExample_Parent_Child_Config_Three_UNSET E_PresenceContainerExample_Parent_Child_Config_Three = 0
+	// PresenceContainerExample_Parent_Child_Config_Three_ONE corresponds to the value ONE of PresenceContainerExample_Parent_Child_Config_Three
+	PresenceContainerExample_Parent_Child_Config_Three_ONE E_PresenceContainerExample_Parent_Child_Config_Three = 1
+	// PresenceContainerExample_Parent_Child_Config_Three_TWO corresponds to the value TWO of PresenceContainerExample_Parent_Child_Config_Three
+	PresenceContainerExample_Parent_Child_Config_Three_TWO E_PresenceContainerExample_Parent_Child_Config_Three = 2
+)
+
+// ΛEnum is a map, keyed by the name of the type defined for each enum in the
+// generated Go code, which provides a mapping between the constant int64 value
+// of each value of the enumeration, and the string that is used to represent it
+// in the YANG schema. The map is named ΛEnum in order to avoid clash with any
+// valid YANG identifier.
+var ΛEnum = map[string]map[int64]ygot.EnumDefinition{
+	"E_PresenceContainerExample_Parent_Child_Config_Three": {
+		1: {Name: "ONE"},
+		2: {Name: "TWO"},
+	},
+}
diff --git a/ygot/render_test.go b/ygot/render_test.go
index c479a85..25c2af8 100644
--- a/ygot/render_test.go
+++ b/ygot/render_test.go
@@ -1658,6 +1658,110 @@ type exampleTransportAddressBinary struct {
 
 func (*exampleTransportAddressBinary) IsExampleTransportAddress() {}
 
+// ucExampleDevice and the following structs are a set of structs used for more
+// complex testing in TestConstructIETFJSON, these structs are used to test for
+// YANG presence containers.
+
+type ucExampleDevice struct {
+	Bgp    *ucExampleBgp    `path:"bgp" yangPresence:"true"`
+	Isis   *ucExampleIsis   `path:"isis"`
+	System *ucExampleSystem `path:"system"`
+}
+
+func (*ucExampleDevice) IsYANGGoStruct()                         {}
+func (*ucExampleDevice) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleDevice) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleDevice) ΛBelongingModule() string                { return "" }
+
+type ucExampleBgp struct {
+	Global   *ucExampleBgpGlobal              `path:"global"`
+	Neighbor map[string]*ucExampleBgpNeighbor `path:"neighbor"`
+}
+
+func (*ucExampleBgp) IsYANGGoStruct()                         {}
+func (*ucExampleBgp) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleBgp) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleBgp) ΛBelongingModule() string                { return "" }
+
+type ucExampleIsis struct {
+	Instance map[string]*ucExampleIsisInstance `path:"instance"`
+}
+
+func (*ucExampleIsis) IsYANGGoStruct()                         {}
+func (*ucExampleIsis) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleIsis) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleIsis) ΛBelongingModule() string                { return "" }
+
+type ucExampleSystem struct {
+	SshServer *ucExampleSystemSshServer `path:"ssh-server" yangPresence:"true"`
+}
+
+func (*ucExampleSystem) IsYANGGoStruct()                         {}
+func (*ucExampleSystem) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleSystem) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleSystem) ΛBelongingModule() string                { return "" }
+
+type ucExampleBgpGlobal struct {
+	As       *uint32 `path:"as"`
+	RouterID *string `path:"router-id"`
+}
+
+func (*ucExampleBgpGlobal) IsYANGGoStruct()                         {}
+func (*ucExampleBgpGlobal) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleBgpGlobal) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleBgpGlobal) ΛBelongingModule() string                { return "" }
+
+type ucExampleBgpNeighbor struct {
+	Description     *string `path:"description"`
+	Enabled         *bool   `path:"enabled"`
+	NeighborAddress *string `path:"neighbor-address"`
+	PeerAs          *uint32 `path:"peer-as"`
+}
+
+func (*ucExampleBgpNeighbor) IsYANGGoStruct()                         {}
+func (*ucExampleBgpNeighbor) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleBgpNeighbor) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleBgpNeighbor) ΛBelongingModule() string                { return "" }
+
+type ucExampleIsisInstance struct {
+	Name     *string                        `path:"name"`
+	Enabled  *bool                          `path:"enabled"`
+	Overload *ucExampleIsisInstanceOverload `path:"overload" yangPresence:"true"`
+}
+
+func (*ucExampleIsisInstance) IsYANGGoStruct()                         {}
+func (*ucExampleIsisInstance) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleIsisInstance) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleIsisInstance) ΛBelongingModule() string                { return "" }
+
+type ucExampleSystemSshServer struct {
+	Instance map[string]*ucExampleSystemSshServer_Instance `path:"instance"`
+}
+
+func (*ucExampleSystemSshServer) IsYANGGoStruct()                         {}
+func (*ucExampleSystemSshServer) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleSystemSshServer) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleSystemSshServer) ΛBelongingModule() string                { return "" }
+
+type ucExampleIsisInstanceOverload struct {
+	AdvertiseExternal *bool `path:"advertise-external"`
+	AdvertiseInternal *bool `path:"advertise-internal"`
+}
+
+func (*ucExampleIsisInstanceOverload) IsYANGGoStruct()                         {}
+func (*ucExampleIsisInstanceOverload) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleIsisInstanceOverload) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleIsisInstanceOverload) ΛBelongingModule() string                { return "" }
+
+type ucExampleSystemSshServer_Instance struct {
+	Name *string `path:"name"`
+}
+
+func (*ucExampleSystemSshServer_Instance) IsYANGGoStruct()                         {}
+func (*ucExampleSystemSshServer_Instance) Validate(...ValidationOption) error      { return nil }
+func (*ucExampleSystemSshServer_Instance) ΛEnumTypeMap() map[string][]reflect.Type { return nil }
+func (*ucExampleSystemSshServer_Instance) ΛBelongingModule() string                { return "" }
+
 // invalidGoStruct explicitly does not implement the ValidatedGoStruct interface.
 type invalidGoStruct struct {
 	Value *string
@@ -2933,6 +3037,245 @@ func TestConstructJSON(t *testing.T) {
 		},
 		wantErr:     true,
 		wantJSONErr: true,
+	}, {
+		name: "uncompressed device example with presence containers with empty value",
+		in: &ucExampleDevice{
+			Bgp: &ucExampleBgp{},
+		},
+		wantIETF: map[string]interface{}{"bgp": map[string]interface{}{}},
+		wantSame: true,
+	}, {
+		name: "uncompressed device example with presence containers with nil value",
+		in: &ucExampleDevice{
+			Bgp: nil,
+		},
+		wantIETF: map[string]interface{}{},
+		wantSame: true,
+	}, {
+		name: "uncompressed device example with presence containers with data in tree",
+		in: &ucExampleDevice{
+			Bgp: &ucExampleBgp{
+				Neighbor: map[string]*ucExampleBgpNeighbor{
+					"192.0.2.1": {
+						Description:     String("a neighbor"),
+						Enabled:         Bool(true),
+						NeighborAddress: String("192.0.2.1"),
+						PeerAs:          Uint32(29636),
+					},
+					"100.64.32.96": {
+						Description:     String("a second neighbor"),
+						Enabled:         Bool(false),
+						NeighborAddress: String("100.64.32.96"),
+						PeerAs:          Uint32(5413),
+					},
+				},
+			},
+		},
+		wantIETF: map[string]interface{}{
+			"bgp": map[string]interface{}{
+				"neighbor": []interface{}{
+					map[string]interface{}{
+						"description":      "a second neighbor",
+						"enabled":          false,
+						"neighbor-address": "100.64.32.96",
+						"peer-as":          5413,
+					},
+					map[string]interface{}{
+						"description":      "a neighbor",
+						"enabled":          true,
+						"neighbor-address": "192.0.2.1",
+						"peer-as":          29636,
+					},
+				},
+			},
+		},
+		wantInternal: map[string]interface{}{
+			"bgp": map[string]interface{}{
+				"neighbor": map[string]interface{}{
+					"192.0.2.1": map[string]interface{}{
+						"description":      "a neighbor",
+						"enabled":          true,
+						"neighbor-address": "192.0.2.1",
+						"peer-as":          29636,
+					},
+					"100.64.32.96": map[string]interface{}{
+						"description":      "a second neighbor",
+						"enabled":          false,
+						"neighbor-address": "100.64.32.96",
+						"peer-as":          5413,
+					},
+				},
+			},
+		},
+	}, {
+		name: "uncompressed device example with presence containers in list with empty and nil value",
+		in: &ucExampleDevice{
+			Isis: &ucExampleIsis{
+				Instance: map[string]*ucExampleIsisInstance{
+					"default": {
+						Name:     String("default"),
+						Enabled:  Bool(true),
+						Overload: &ucExampleIsisInstanceOverload{},
+					},
+					"instance1": {
+						Name:     String("instance1"),
+						Enabled:  Bool(false),
+						Overload: nil,
+					},
+				},
+			},
+		},
+		wantIETF: map[string]interface{}{
+			"isis": map[string]interface{}{
+				"instance": []interface{}{
+					map[string]interface{}{
+						"name":     "default",
+						"enabled":  true,
+						"overload": map[string]interface{}{},
+					},
+					map[string]interface{}{
+						"name":    "instance1",
+						"enabled": false,
+					},
+				},
+			},
+		},
+		wantInternal: map[string]interface{}{
+			"isis": map[string]interface{}{
+				"instance": map[string]interface{}{
+					"default": map[string]interface{}{
+						"name":     "default",
+						"enabled":  true,
+						"overload": map[string]interface{}{},
+					},
+					"instance1": map[string]interface{}{
+						"name":    "instance1",
+						"enabled": false,
+					},
+				},
+			},
+		},
+	}, {
+		name: "uncompressed device example with presence containers in list with data in tree",
+		in: &ucExampleDevice{
+			Isis: &ucExampleIsis{
+				Instance: map[string]*ucExampleIsisInstance{
+					"default": {
+						Name:     String("default"),
+						Enabled:  Bool(true),
+						Overload: &ucExampleIsisInstanceOverload{},
+					},
+					"instance1": {
+						Name:     String("instance1"),
+						Enabled:  Bool(false),
+						Overload: &ucExampleIsisInstanceOverload{AdvertiseExternal: Bool(true), AdvertiseInternal: Bool(false)},
+					},
+				},
+			},
+		},
+		wantIETF: map[string]interface{}{
+			"isis": map[string]interface{}{
+				"instance": []interface{}{
+					map[string]interface{}{
+						"name":     "default",
+						"enabled":  true,
+						"overload": map[string]interface{}{},
+					},
+					map[string]interface{}{
+						"name":    "instance1",
+						"enabled": false,
+						"overload": map[string]interface{}{
+							"advertise-external": true,
+							"advertise-internal": false,
+						},
+					},
+				},
+			},
+		},
+		wantInternal: map[string]interface{}{
+			"isis": map[string]interface{}{
+				"instance": map[string]interface{}{
+					"default": map[string]interface{}{
+						"name":     "default",
+						"enabled":  true,
+						"overload": map[string]interface{}{},
+					},
+					"instance1": map[string]interface{}{
+						"name":    "instance1",
+						"enabled": false,
+						"overload": map[string]interface{}{
+							"advertise-external": true,
+							"advertise-internal": false,
+						},
+					},
+				},
+			},
+		},
+	}, {
+		name: "uncompressed device example with presence container encapsulated in regular container with nil value",
+		in: &ucExampleDevice{
+			Bgp: &ucExampleBgp{},
+			System: &ucExampleSystem{
+				SshServer: nil,
+			},
+		},
+		wantIETF: map[string]interface{}{
+			"bgp": map[string]interface{}{},
+		},
+		wantSame: true,
+	}, {
+		name: "uncompressed device example with presence container encapsulated in regular container with empty value",
+		in: &ucExampleDevice{
+			Bgp: &ucExampleBgp{},
+			System: &ucExampleSystem{
+				SshServer: &ucExampleSystemSshServer{},
+			},
+		},
+		wantIETF: map[string]interface{}{
+			"bgp": map[string]interface{}{},
+			"system": map[string]interface{}{
+				"ssh-server": map[string]interface{}{},
+			},
+		},
+		wantSame: true,
+	}, {
+		name: "uncompressed device example with presence container encapsulated in regular container with data in tree",
+		in: &ucExampleDevice{
+			Bgp: &ucExampleBgp{},
+			System: &ucExampleSystem{
+				SshServer: &ucExampleSystemSshServer{
+					Instance: map[string]*ucExampleSystemSshServer_Instance{
+						"mgmt": {
+							Name: String("system"),
+						},
+					},
+				},
+			},
+		},
+		wantIETF: map[string]interface{}{
+			"bgp": map[string]interface{}{},
+			"system": map[string]interface{}{
+				"ssh-server": map[string]interface{}{
+					"instance": []interface{}{
+						map[string]interface{}{
+							"name": "system",
+						},
+					},
+				},
+			},
+		},
+		wantInternal: map[string]interface{}{
+			"bgp": map[string]interface{}{},
+			"system": map[string]interface{}{
+				"ssh-server": map[string]interface{}{
+					"instance": map[string]interface{}{
+						"mgmt": map[string]interface{}{
+							"name": "system",
+						},
+					},
+				},
+			},
+		},
 	}, {
 		name:     "unset enum",
 		in:       &renderExample{EnumField: EnumTestUNSET},
diff --git a/ytypes/container_test.go b/ytypes/container_test.go
index 4622c41..42012b0 100644
--- a/ytypes/container_test.go
+++ b/ytypes/container_test.go
@@ -306,6 +306,20 @@ func TestUnmarshalContainer(t *testing.T) {
 				Name: "leaf2-field",
 				Type: &yang.YangType{Kind: yang.Yint32},
 			},
+			"container2-field": {
+				Name: "container2-field",
+				Kind: yang.DirectoryEntry,
+				Dir: map[string]*yang.Entry{
+					"leaf3-field": {
+						Kind: yang.LeafEntry,
+						Name: "leaf3-field",
+						Type: &yang.YangType{Kind: yang.Yint32},
+					},
+				},
+				Extra: map[string][]interface{}{
+					"presence": {&yang.Value{Name: "presence container2-field"}},
+				},
+			},
 		},
 	}
 	containerSchema := &yang.Entry{
@@ -318,26 +332,33 @@ func TestUnmarshalContainer(t *testing.T) {
 
 	populateParentField(nil, containerSchema)
 
+	type Container2Struct struct {
+		Leaf3Field *int32 `path:"leaf3-field"`
+	}
+
 	type ContainerStruct struct {
 		ConfigLeaf1Field *int32            `path:"config/leaf1-field"`
 		StateLeaf1Field  *int32            `path:"state/leaf1-field"`
 		Leaf2Field       *int32            `path:"leaf2-field"`
+		Container2Field  *Container2Struct `path:"container2-field" yangPresence:"true"`
 		Annotation       []ygot.Annotation `path:"@" ygotAnnotation:"true"`
 		AnnotationTwo    []ygot.Annotation `path:"@one|@two" ygotAnnotation:"true"`
 	}
 
 	type ContainerStructPreferState struct {
-		Leaf1Field    *int32            `path:"state/leaf1-field" shadow-path:"config/leaf1-field"`
-		Leaf2Field    *int32            `path:"leaf2-field"`
-		Annotation    []ygot.Annotation `path:"@" ygotAnnotation:"true"`
-		AnnotationTwo []ygot.Annotation `path:"@one|@two" ygotAnnotation:"true"`
+		Leaf1Field      *int32            `path:"state/leaf1-field" shadow-path:"config/leaf1-field"`
+		Leaf2Field      *int32            `path:"leaf2-field"`
+		Container2Field *Container2Struct `path:"container2-field" yangPresence:"true"`
+		Annotation      []ygot.Annotation `path:"@" ygotAnnotation:"true"`
+		AnnotationTwo   []ygot.Annotation `path:"@one|@two" ygotAnnotation:"true"`
 	}
 
 	type ContainerStructPreferStateNoShadow struct {
-		Leaf1Field    *int32            `path:"state/leaf1-field"`
-		Leaf2Field    *int32            `path:"leaf2-field"`
-		Annotation    []ygot.Annotation `path:"@" ygotAnnotation:"true"`
-		AnnotationTwo []ygot.Annotation `path:"@one|@two" ygotAnnotation:"true"`
+		Leaf1Field      *int32            `path:"state/leaf1-field"`
+		Leaf2Field      *int32            `path:"leaf2-field"`
+		Container2Field *Container2Struct `path:"container2-field" yangPresence:"true"`
+		Annotation      []ygot.Annotation `path:"@" ygotAnnotation:"true"`
+		AnnotationTwo   []ygot.Annotation `path:"@one|@two" ygotAnnotation:"true"`
 	}
 
 	type ParentContainerStruct struct {
@@ -373,14 +394,42 @@ func TestUnmarshalContainer(t *testing.T) {
 			schema: containerSchema,
 			parent: &ParentContainerStruct{},
 			json:   `{ "container-field": { "leaf2-field": 43, "config": { "leaf1-field": 41 } , "state": { "leaf1-field": 42 } } }`,
-			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43)}},
+			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43), Container2Field: nil}},
+		},
+		{
+			desc:   "success with presence container",
+			schema: containerSchema,
+			parent: &ParentContainerStruct{},
+			json:   `{ "container-field": { "leaf2-field": 43, "config": { "leaf1-field": 41 } , "state": { "leaf1-field": 42 } , "container2-field": { "leaf3-field": 44 } } }`,
+			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43), Container2Field: &Container2Struct{Leaf3Field: ygot.Int32(44)}}},
+		},
+		{
+			desc:   "success with empty presence container",
+			schema: containerSchema,
+			parent: &ParentContainerStruct{},
+			json:   `{ "container-field": { "leaf2-field": 43, "config": { "leaf1-field": 41 } , "state": { "leaf1-field": 42 } , "container2-field": {  } } }`,
+			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43), Container2Field: &Container2Struct{}}},
 		},
 		{
 			desc:   "success overwriting existing fields",
 			schema: containerSchema,
 			parent: &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(1), StateLeaf1Field: ygot.Int32(2)}},
 			json:   `{ "container-field": { "leaf2-field": 43, "config": { "leaf1-field": 41 } , "state": { "leaf1-field": 42 } } }`,
-			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43)}},
+			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43), Container2Field: nil}},
+		},
+		{
+			desc:   "success overwriting existing fields with presence container",
+			schema: containerSchema,
+			parent: &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(1), StateLeaf1Field: ygot.Int32(2), Container2Field: &Container2Struct{Leaf3Field: ygot.Int32(3)}}},
+			json:   `{ "container-field": { "leaf2-field": 43, "config": { "leaf1-field": 41 } , "state": { "leaf1-field": 42 } , "container2-field": { "leaf3-field": 44 } } }`,
+			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43), Container2Field: &Container2Struct{Leaf3Field: ygot.Int32(44)}}},
+		},
+		{
+			desc:   "success overwriting existing fields, no-op on empty presence container",
+			schema: containerSchema,
+			parent: &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(1), StateLeaf1Field: ygot.Int32(2), Container2Field: &Container2Struct{Leaf3Field: ygot.Int32(3)}}},
+			json:   `{ "container-field": { "leaf2-field": 43, "config": { "leaf1-field": 41 } , "state": { "leaf1-field": 42 } , "container2-field": {  } } }`,
+			want:   &ParentContainerStruct{ContainerField: &ContainerStruct{ConfigLeaf1Field: ygot.Int32(41), StateLeaf1Field: ygot.Int32(42), Leaf2Field: ygot.Int32(43), Container2Field: &Container2Struct{Leaf3Field: ygot.Int32(3)}}},
 		},
 		{
 			desc:    "nil schema",