diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -231,6 +231,7 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
         new_namespace = {
             '__config__': config,
             '__fields__': fields,
+            '__field_defaults__': {n: f.default for n, f in fields.items() if not f.required},
             '__validators__': vg.validators,
             '__pre_root_validators__': pre_root_validators + pre_rv_new,
             '__post_root_validators__': post_root_validators + post_rv_new,
@@ -252,6 +253,7 @@ class BaseModel(metaclass=ModelMetaclass):
     if TYPE_CHECKING:
         # populated by the metaclass, defined here to help IDEs only
         __fields__: Dict[str, ModelField] = {}
+        __field_defaults__: Dict[str, Any] = {}
         __validators__: Dict[str, AnyCallable] = {}
         __pre_root_validators__: List[AnyCallable]
         __post_root_validators__: List[AnyCallable]
@@ -303,15 +305,23 @@ def dict(
         include: Union['SetIntStr', 'DictIntStrAny'] = None,
         exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
         by_alias: bool = False,
-        skip_defaults: bool = False,
+        skip_defaults: bool = None,
+        exclude_unset: bool = False,
+        exclude_defaults: bool = False,
     ) -> 'DictStrAny':
         """
         Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
         """
+        if skip_defaults is not None:
+            warnings.warn(
+                f'{self.__class__.__name__}.dict(): "skip_defaults" is deprecated and replaced by "exclude_unset"',
+                DeprecationWarning,
+            )
+            exclude_unset = skip_defaults
         get_key = self._get_key_factory(by_alias)
         get_key = partial(get_key, self.__fields__)
 
-        allowed_keys = self._calculate_keys(include=include, exclude=exclude, skip_defaults=skip_defaults)
+        allowed_keys = self._calculate_keys(include=include, exclude=exclude, exclude_unset=exclude_unset)
         return {
             get_key(k): v
             for k, v in self._iter(
@@ -320,7 +330,8 @@ def dict(
                 allowed_keys=allowed_keys,
                 include=include,
                 exclude=exclude,
-                skip_defaults=skip_defaults,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
             )
         }
 
@@ -336,7 +347,9 @@ def json(
         include: Union['SetIntStr', 'DictIntStrAny'] = None,
         exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
         by_alias: bool = False,
-        skip_defaults: bool = False,
+        skip_defaults: bool = None,
+        exclude_unset: bool = False,
+        exclude_defaults: bool = False,
         encoder: Optional[Callable[[Any], Any]] = None,
         **dumps_kwargs: Any,
     ) -> str:
@@ -345,8 +358,20 @@ def json(
 
         `encoder` is an optional function to supply as `default` to json.dumps(), other arguments as per `json.dumps()`.
         """
+        if skip_defaults is not None:
+            warnings.warn(
+                f'{self.__class__.__name__}.json(): "skip_defaults" is deprecated and replaced by "exclude_unset"',
+                DeprecationWarning,
+            )
+            exclude_unset = skip_defaults
         encoder = cast(Callable[[Any], Any], encoder or self.__json_encoder__)
-        data = self.dict(include=include, exclude=exclude, by_alias=by_alias, skip_defaults=skip_defaults)
+        data = self.dict(
+            include=include,
+            exclude=exclude,
+            by_alias=by_alias,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+        )
         if self.__custom_root_type__:
             data = data[ROOT_KEY]
         return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs)
@@ -446,7 +471,7 @@ def copy(
             # skip constructing values if no arguments are passed
             v = self.__dict__
         else:
-            allowed_keys = self._calculate_keys(include=include, exclude=exclude, skip_defaults=False, update=update)
+            allowed_keys = self._calculate_keys(include=include, exclude=exclude, exclude_unset=False, update=update)
             if allowed_keys is None:
                 v = {**self.__dict__, **(update or {})}
             else:
@@ -457,7 +482,7 @@ def copy(
                             by_alias=False,
                             include=include,
                             exclude=exclude,
-                            skip_defaults=False,
+                            exclude_unset=False,
                             allowed_keys=allowed_keys,
                         )
                     ),
@@ -516,12 +541,19 @@ def _get_value(
         by_alias: bool,
         include: Optional[Union['SetIntStr', 'DictIntStrAny']],
         exclude: Optional[Union['SetIntStr', 'DictIntStrAny']],
-        skip_defaults: bool,
+        exclude_unset: bool,
+        exclude_defaults: bool,
     ) -> Any:
 
         if isinstance(v, BaseModel):
             if to_dict:
-                return v.dict(by_alias=by_alias, skip_defaults=skip_defaults, include=include, exclude=exclude)
+                return v.dict(
+                    by_alias=by_alias,
+                    exclude_unset=exclude_unset,
+                    exclude_defaults=exclude_defaults,
+                    include=include,
+                    exclude=exclude,
+                )
             else:
                 return v.copy(include=include, exclude=exclude)
 
@@ -534,7 +566,8 @@ def _get_value(
                     v_,
                     to_dict=to_dict,
                     by_alias=by_alias,
-                    skip_defaults=skip_defaults,
+                    exclude_unset=exclude_unset,
+                    exclude_defaults=exclude_defaults,
                     include=value_include and value_include.for_element(k_),
                     exclude=value_exclude and value_exclude.for_element(k_),
                 )
@@ -549,7 +582,8 @@ def _get_value(
                     v_,
                     to_dict=to_dict,
                     by_alias=by_alias,
-                    skip_defaults=skip_defaults,
+                    exclude_unset=exclude_unset,
+                    exclude_defaults=exclude_defaults,
                     include=value_include and value_include.for_element(i),
                     exclude=value_exclude and value_exclude.for_element(i),
                 )
@@ -584,12 +618,20 @@ def _iter(
         allowed_keys: Optional['SetStr'] = None,
         include: Union['SetIntStr', 'DictIntStrAny'] = None,
         exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
-        skip_defaults: bool = False,
+        exclude_unset: bool = False,
+        exclude_defaults: bool = False,
     ) -> 'TupleGenerator':
 
         value_exclude = ValueItems(self, exclude) if exclude else None
         value_include = ValueItems(self, include) if include else None
 
+        if exclude_defaults:
+            if allowed_keys is None:
+                allowed_keys = set(self.__fields__)
+            for k, v in self.__field_defaults__.items():
+                if self.__dict__[k] == v:
+                    allowed_keys.discard(k)
+
         for k, v in self.__dict__.items():
             if allowed_keys is None or k in allowed_keys:
                 yield k, self._get_value(
@@ -598,20 +640,21 @@ def _iter(
                     by_alias=by_alias,
                     include=value_include and value_include.for_element(k),
                     exclude=value_exclude and value_exclude.for_element(k),
-                    skip_defaults=skip_defaults,
+                    exclude_unset=exclude_unset,
+                    exclude_defaults=exclude_defaults,
                 )
 
     def _calculate_keys(
         self,
         include: Optional[Union['SetIntStr', 'DictIntStrAny']],
         exclude: Optional[Union['SetIntStr', 'DictIntStrAny']],
-        skip_defaults: bool,
+        exclude_unset: bool,
         update: Optional['DictStrAny'] = None,
     ) -> Optional['SetStr']:
-        if include is None and exclude is None and skip_defaults is False:
+        if include is None and exclude is None and exclude_unset is False:
             return None
 
-        if skip_defaults:
+        if exclude_unset:
             keys = self.__fields_set__.copy()
         else:
             keys = set(self.__dict__.keys())

I agree that it should, PR welcome
@skewty in this case you are explicitly passing a value to `PanelData.validate` with `'gios_type': DIGITAL_INPUT`.

You are passing the same value as the one that would be used by default, but you are passing it explicitly.

If you don't pass an explicit value, and let the model take the default one (the same value, but using the default), then Pydantic respects the `skip_defaults`:

```Python
from typing import List
from pydantic import BaseModel, Schema

DIGITAL_INPUT = 'digital_input'


class Payload(BaseModel):
    """Base class for all Combox payloads"""


class PointData(Payload):
    name: str = Schema(..., title='Name', min_length=1, max_length=32)
    gios_type: str = Schema(DIGITAL_INPUT, title='GIOS Type')


class PanelData(Payload):
    name: str = Schema(..., title='Name', min_length=1, max_length=32)
    points: List[PointData] = Schema(..., title='Points', min_length=1)


pd = PanelData.validate({'name': 'Dummy Panel', 'points': [{'name': 'Dummy Point'}]})
assert 'gios_type' not in pd.dict(skip_defaults=True)['points'][0]
print(pd.dict(skip_defaults=True))
```

---

I consider this behavior (the current behavior) a feature (useful), for example in this example:

Let's say you have this model:

```Python
class Person(BaseModel):
    name: str
    pet: str = None
```

You have an item in your DB that contains:

```Python
{"name": "alice", "pet": "orion"}
```

Now let's say you receive an update to that item with:

```Python
{"name": "bob"}
```

As `pet` is not set, you can keep the current value of `"orion"`, but you can update the name to `"bob"`. Using `skip_defaults` skips the non-existing `"pet"` that would be set to `None` by default.

Now in your DB:

```Python
{"name": "bob", "pet": "orion"}
```

Then, if you receive an update to the same item with:

```Python
{"pet": None}
```

You could update your item in the DB, marking the pet as dead (`None`).

Now in your DB:

```Python
{"name": "bob", "pet": None}
```

In this case, Pydantic would be smart enough to know that `pet` was intentionally and explicitly set to `None`, even when that was the default value.

It's able to distinguish between values taken by default and values set, even if the value is the same.
@tiangolo thank you for taking the time to make such a complete reply and sorry for taking so long to get back to this.

> I consider this behavior (the current behavior) a feature (useful), for example in this example:
> ...
> In this case, Pydantic would be smart enough to know that `pet` was intentionally and explicitly set to `None`, even when that was the default value.
> 
> It's able to distinguish between values taken by default and values set, even if the value is the same.

To me anyway, it is surprising that " It's able to distinguish between values taken by default and values set, even if the value is the same."  To me, I see the feature more as a `skip_unset_defaults=True` rather than a `skip_defaults=True` because you are not really skipping default values as the parameter name suggests (to me).

That said, would an alternate parameter name be acceptable to accomplish my version be acceptable?  Or perhaps some sort of additional parameter like `strict=True` which I would set to `False` to accomplish what I was looking for when I created this issue?
I realize changing how `skip_defaults` parameter works would be a breaking change and such a change would be best done before v1.0.  So this issue / discussion is coming up at a good time :)
I'm not entirely clear what change/new feature you would like?
@samuelcolvin I think the request is to change the `skip_defaults` argument of `BaseModel.dict` to exclude values if they are equal to the default value (whether or not it was set), and to have a new keyword argument (called `skip_unset_defaults`; I would propose just `skip_unset`) that implements the existing functionality.

For what it's worth, I also found the `skip_defaults` behavior surprising at first; `skip_unset` might have been a clearer name, especially considering most other libraries wouldn't even be *capable* of supporting the `skip_defaults` feature, so I think most people wouldn't guess that's how it works. Separately, I feel like skipping defaults (whether or not they were set) is a relatively common feature request. (For example, it would make it a lot easier to skip nulls in many cases).

At this point I'm familiar and comfortable with the existing implementation, so I'm fine either way, but I can see some merits.
`skip_defaults` and `skip_unset` sounds reasonable to me.

One advantage is that there's no more logic required on parsing so performance (of raw parsing) would be unchanged.

My only concern is that `skip_defaults` has new behaviour but has a name that shadows a different old keyword argument which now has a new name. This will cause a lot of headaches when migrating to v1 since tests and setup won't fail immediately.

One solution would be to completely change the names to: `exclude_defaults` and `exclude_unset`. That way there can be no confusion with the names and it's more consistent with the `exclude` argument at the expensive for 3 more characters and one more syllable.

What do you think?
I like the consistency with `exclude`.
do we still want this before v1 is released?

If so it needs someone to start it in the next week or so.
I think it's worthwhile. I'll try to get to this this weekend if no one else wants to handle it sooner.
might require logic shared to #898 to get the default values.
diff --git a/tests/test_construction.py b/tests/test_construction.py
--- a/tests/test_construction.py
+++ b/tests/test_construction.py
@@ -181,8 +181,8 @@ def test_copy_set_fields():
     m = ModelTwo(a=24, d=Model(a='12'))
     m2 = m.copy()
 
-    assert m.dict(skip_defaults=True) == {'a': 24.0, 'd': {'a': 12}}
-    assert m.dict(skip_defaults=True) == m2.dict(skip_defaults=True)
+    assert m.dict(exclude_unset=True) == {'a': 24.0, 'd': {'a': 12}}
+    assert m.dict(exclude_unset=True) == m2.dict(exclude_unset=True)
 
 
 def test_simple_pickle():
@@ -227,9 +227,9 @@ class Config:
 
 def test_pickle_fields_set():
     m = Model(a=24)
-    assert m.dict(skip_defaults=True) == {'a': 24}
+    assert m.dict(exclude_unset=True) == {'a': 24}
     m2 = pickle.loads(pickle.dumps(m))
-    assert m2.dict(skip_defaults=True) == {'a': 24}
+    assert m2.dict(exclude_unset=True) == {'a': 24}
 
 
 def test_copy_update_exclude():
@@ -246,5 +246,5 @@ class Model(BaseModel):
     assert m.copy(exclude={'c'}).dict() == {'d': {'a': 'ax', 'b': 'bx'}}
     assert m.copy(exclude={'c'}, update={'c': 42}).dict() == {'c': 42, 'd': {'a': 'ax', 'b': 'bx'}}
 
-    assert m._calculate_keys(exclude={'x'}, include=None, skip_defaults=False) == {'c', 'd'}
-    assert m._calculate_keys(exclude={'x'}, include=None, skip_defaults=False, update={'c': 42}) == {'d'}
+    assert m._calculate_keys(exclude={'x'}, include=None, exclude_unset=False) == {'c', 'd'}
+    assert m._calculate_keys(exclude={'x'}, include=None, exclude_unset=False, update={'c': 42}) == {'d'}
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -373,26 +373,70 @@ class Model(BaseModel):
     assert m.dict(include={'a', 'b'}, exclude={'a'}) == {'b': 2}
 
 
-def test_include_exclude_default():
+def test_include_exclude_unset():
     class Model(BaseModel):
         a: int
         b: int
         c: int = 3
         d: int = 4
+        e: int = 5
+        f: int = 6
 
-    m = Model(a=1, b=2)
-    assert m.dict() == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
-    assert m.__fields_set__ == {'a', 'b'}
-    assert m.dict(skip_defaults=True) == {'a': 1, 'b': 2}
+    m = Model(a=1, b=2, e=5, f=7)
+    assert m.dict() == {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}
+    assert m.__fields_set__ == {'a', 'b', 'e', 'f'}
+    assert m.dict(exclude_unset=True) == {'a': 1, 'b': 2, 'e': 5, 'f': 7}
+
+    assert m.dict(include={'a'}, exclude_unset=True) == {'a': 1}
+    assert m.dict(include={'c'}, exclude_unset=True) == {}
+
+    assert m.dict(exclude={'a'}, exclude_unset=True) == {'b': 2, 'e': 5, 'f': 7}
+    assert m.dict(exclude={'c'}, exclude_unset=True) == {'a': 1, 'b': 2, 'e': 5, 'f': 7}
+
+    assert m.dict(include={'a', 'b', 'c'}, exclude={'b'}, exclude_unset=True) == {'a': 1}
+    assert m.dict(include={'a', 'b', 'c'}, exclude={'a', 'c'}, exclude_unset=True) == {'b': 2}
 
-    assert m.dict(include={'a'}, skip_defaults=True) == {'a': 1}
-    assert m.dict(include={'c'}, skip_defaults=True) == {}
 
-    assert m.dict(exclude={'a'}, skip_defaults=True) == {'b': 2}
-    assert m.dict(exclude={'c'}, skip_defaults=True) == {'a': 1, 'b': 2}
+def test_include_exclude_defaults():
+    class Model(BaseModel):
+        a: int
+        b: int
+        c: int = 3
+        d: int = 4
+        e: int = 5
+        f: int = 6
+
+    m = Model(a=1, b=2, e=5, f=7)
+    assert m.dict() == {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}
+    assert m.__fields_set__ == {'a', 'b', 'e', 'f'}
+    assert m.dict(exclude_defaults=True) == {'a': 1, 'b': 2, 'f': 7}
+
+    assert m.dict(include={'a'}, exclude_defaults=True) == {'a': 1}
+    assert m.dict(include={'c'}, exclude_defaults=True) == {}
+
+    assert m.dict(exclude={'a'}, exclude_defaults=True) == {'b': 2, 'f': 7}
+    assert m.dict(exclude={'c'}, exclude_defaults=True) == {'a': 1, 'b': 2, 'f': 7}
+
+    assert m.dict(include={'a', 'b', 'c'}, exclude={'b'}, exclude_defaults=True) == {'a': 1}
+    assert m.dict(include={'a', 'b', 'c'}, exclude={'a', 'c'}, exclude_defaults=True) == {'b': 2}
+
+
+def test_skip_defaults_deprecated():
+    class Model(BaseModel):
+        x: int
+
+    m = Model(x=1)
+    match = r'Model.dict\(\): "skip_defaults" is deprecated and replaced by "exclude_unset"'
+    with pytest.warns(DeprecationWarning, match=match):
+        assert m.dict(skip_defaults=True)
+    with pytest.warns(DeprecationWarning, match=match):
+        assert m.dict(skip_defaults=False)
 
-    assert m.dict(include={'a', 'b', 'c'}, exclude={'b'}, skip_defaults=True) == {'a': 1}
-    assert m.dict(include={'a', 'b', 'c'}, exclude={'a', 'c'}, skip_defaults=True) == {'b': 2}
+    match = r'Model.json\(\): "skip_defaults" is deprecated and replaced by "exclude_unset"'
+    with pytest.warns(DeprecationWarning, match=match):
+        assert m.json(skip_defaults=True)
+    with pytest.warns(DeprecationWarning, match=match):
+        assert m.json(skip_defaults=False)
 
 
 def test_advanced_exclude():
@@ -474,12 +518,12 @@ class Config:
     m = Model(a=1, b=2)
     assert m.dict() == {'a': 1, 'b': 2, 'c': 3}
     assert m.__fields_set__ == {'a', 'b'}
-    assert m.dict(skip_defaults=True) == {'a': 1, 'b': 2}
+    assert m.dict(exclude_unset=True) == {'a': 1, 'b': 2}
 
     m2 = Model(a=1, b=2, d=4)
     assert m2.dict() == {'a': 1, 'b': 2, 'c': 3}
     assert m2.__fields_set__ == {'a', 'b'}
-    assert m2.dict(skip_defaults=True) == {'a': 1, 'b': 2}
+    assert m2.dict(exclude_unset=True) == {'a': 1, 'b': 2}
 
 
 def test_field_set_allow_extra():
@@ -494,12 +538,12 @@ class Config:
     m = Model(a=1, b=2)
     assert m.dict() == {'a': 1, 'b': 2, 'c': 3}
     assert m.__fields_set__ == {'a', 'b'}
-    assert m.dict(skip_defaults=True) == {'a': 1, 'b': 2}
+    assert m.dict(exclude_unset=True) == {'a': 1, 'b': 2}
 
     m2 = Model(a=1, b=2, d=4)
     assert m2.dict() == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
     assert m2.__fields_set__ == {'a', 'b', 'd'}
-    assert m2.dict(skip_defaults=True) == {'a': 1, 'b': 2, 'd': 4}
+    assert m2.dict(exclude_unset=True) == {'a': 1, 'b': 2, 'd': 4}
 
 
 def test_field_set_field_name():
@@ -509,7 +553,7 @@ class Model(BaseModel):
         b: int = 3
 
     assert Model(a=1, field_set=2).dict() == {'a': 1, 'field_set': 2, 'b': 3}
-    assert Model(a=1, field_set=2).dict(skip_defaults=True) == {'a': 1, 'field_set': 2}
+    assert Model(a=1, field_set=2).dict(exclude_unset=True) == {'a': 1, 'field_set': 2}
     assert Model.construct(dict(a=1, field_set=3), {'a', 'field_set'}).dict() == {'a': 1, 'field_set': 3}
 
 
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -729,19 +729,19 @@ class MyModel(BaseModel):
     assert m.__fields_set__ == {'a', 'b'}
 
 
-def test_skip_defaults_dict():
+def test_exclude_unset_dict():
     class MyModel(BaseModel):
         a: int
         b: int = 2
 
     m = MyModel(a=5)
-    assert m.dict(skip_defaults=True) == {'a': 5}
+    assert m.dict(exclude_unset=True) == {'a': 5}
 
     m = MyModel(a=5, b=3)
-    assert m.dict(skip_defaults=True) == {'a': 5, 'b': 3}
+    assert m.dict(exclude_unset=True) == {'a': 5, 'b': 3}
 
 
-def test_skip_defaults_recursive():
+def test_exclude_unset_recursive():
     class ModelA(BaseModel):
         a: int
         b: int = 1
@@ -753,11 +753,11 @@ class ModelB(BaseModel):
 
     m = ModelB(c=5, e={'a': 0})
     assert m.dict() == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}}
-    assert m.dict(skip_defaults=True) == {'c': 5, 'e': {'a': 0}}
+    assert m.dict(exclude_unset=True) == {'c': 5, 'e': {'a': 0}}
     assert dict(m) == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}}
 
 
-def test_dict_skip_defaults_populated_by_alias():
+def test_dict_exclude_unset_populated_by_alias():
     class MyModel(BaseModel):
         a: str = Field('default', alias='alias_a')
         b: str = Field('default', alias='alias_b')
@@ -767,11 +767,11 @@ class Config:
 
     m = MyModel(alias_a='a')
 
-    assert m.dict(skip_defaults=True) == {'a': 'a'}
-    assert m.dict(skip_defaults=True, by_alias=True) == {'alias_a': 'a'}
+    assert m.dict(exclude_unset=True) == {'a': 'a'}
+    assert m.dict(exclude_unset=True, by_alias=True) == {'alias_a': 'a'}
 
 
-def test_dict_skip_defaults_populated_by_alias_with_extra():
+def test_dict_exclude_unset_populated_by_alias_with_extra():
     class MyModel(BaseModel):
         a: str = Field('default', alias='alias_a')
         b: str = Field('default', alias='alias_b')
@@ -781,8 +781,8 @@ class Config:
 
     m = MyModel(alias_a='a', c='c')
 
-    assert m.dict(skip_defaults=True) == {'a': 'a', 'c': 'c'}
-    assert m.dict(skip_defaults=True, by_alias=True) == {'alias_a': 'a', 'c': 'c'}
+    assert m.dict(exclude_unset=True) == {'a': 'a', 'c': 'c'}
+    assert m.dict(exclude_unset=True, by_alias=True) == {'alias_a': 'a', 'c': 'c'}
 
 
 def test_dir_fields():
diff --git a/tests/test_orm_mode.py b/tests/test_orm_mode.py
--- a/tests/test_orm_mode.py
+++ b/tests/test_orm_mode.py
@@ -122,7 +122,7 @@ class Config:
     model = Model.from_orm(foo)
     assert model.foo == 'Foo'
     assert model.bar == 1
-    assert model.dict(skip_defaults=True) == {'foo': 'Foo'}
+    assert model.dict(exclude_unset=True) == {'foo': 'Foo'}
     with pytest.raises(ValidationError):
         ModelInvalid.from_orm(foo)
 

dict(skip_defaults=True) doesn't apply to nested models

```python
from typing import List
from pydantic import BaseModel, Schema

DIGITAL_INPUT = 'digital_input'


class Payload(BaseModel):
    """Base class for all Combox payloads"""


class PointData(Payload):
    name: str = Schema(..., title='Name', min_length=1, max_length=32)
    gios_type: str = Schema(DIGITAL_INPUT, title='GIOS Type')


class PanelData(Payload):
    name: str = Schema(..., title='Name', min_length=1, max_length=32)
    points: List[PointData] = Schema(..., title='Points', min_length=1)


pd = PanelData.validate({'name': 'Dummy Panel', 'points': [{'name': 'Dummy Point', 'gios_type': DIGITAL_INPUT}]})
assert pd.dict(skip_defaults=True)['points'][0]['gios_type'] == DIGITAL_INPUT
print(pd.dict(skip_defaults=True))
```

```
{'name': 'Dummy Panel', 'points': [{'name': 'Dummy Point', 'gios_type': 'digital_input'}]}
```

Notice how the `dict` contains 'gios_type' field.

tests/test_construction.py::test_copy_set_fields tests/test_construction.py::test_pickle_fields_set tests/test_construction.py::test_copy_update_exclude tests/test_edge_cases.py::test_include_exclude_unset tests/test_edge_cases.py::test_include_exclude_defaults tests/test_edge_cases.py::test_skip_defaults_deprecated tests/test_edge_cases.py::test_field_set_ignore_extra tests/test_edge_cases.py::test_field_set_allow_extra tests/test_edge_cases.py::test_field_set_field_name tests/test_main.py::test_exclude_unset_dict tests/test_main.py::test_exclude_unset_recursive tests/test_main.py::test_dict_exclude_unset_populated_by_alias tests/test_main.py::test_dict_exclude_unset_populated_by_alias_with_extra tests/test_orm_mode.py::test_object_with_getattr
tests/test_construction.py::test_simple_construct tests/test_construction.py::test_construct_missing tests/test_construction.py::test_large_any_str tests/test_construction.py::test_simple_copy tests/test_construction.py::test_deep_copy tests/test_construction.py::test_copy_exclude tests/test_construction.py::test_copy_include tests/test_construction.py::test_copy_include_exclude tests/test_construction.py::test_copy_advanced_exclude tests/test_construction.py::test_copy_advanced_include tests/test_construction.py::test_copy_advanced_include_exclude tests/test_construction.py::test_copy_update tests/test_construction.py::test_simple_pickle tests/test_construction.py::test_recursive_pickle tests/test_construction.py::test_immutable_copy tests/test_edge_cases.py::test_str_bytes tests/test_edge_cases.py::test_str_bytes_none tests/test_edge_cases.py::test_union_int_str tests/test_edge_cases.py::test_union_priority tests/test_edge_cases.py::test_typed_list tests/test_edge_cases.py::test_typed_set tests/test_edge_cases.py::test_dict_dict tests/test_edge_cases.py::test_typed_dict[value0-result0] tests/test_edge_cases.py::test_typed_dict[value1-result1] tests/test_edge_cases.py::test_typed_dict[value2-result2] tests/test_edge_cases.py::test_typed_dict_error[1-errors0] tests/test_edge_cases.py::test_typed_dict_error[value1-errors1] tests/test_edge_cases.py::test_typed_dict_error[value2-errors2] tests/test_edge_cases.py::test_dict_key_error tests/test_edge_cases.py::test_tuple tests/test_edge_cases.py::test_tuple_more tests/test_edge_cases.py::test_tuple_length_error tests/test_edge_cases.py::test_tuple_invalid tests/test_edge_cases.py::test_tuple_value_error tests/test_edge_cases.py::test_recursive_list tests/test_edge_cases.py::test_recursive_list_error tests/test_edge_cases.py::test_list_unions tests/test_edge_cases.py::test_recursive_lists tests/test_edge_cases.py::test_str_enum tests/test_edge_cases.py::test_any_dict tests/test_edge_cases.py::test_infer_alias tests/test_edge_cases.py::test_alias_error tests/test_edge_cases.py::test_annotation_config tests/test_edge_cases.py::test_success_values_include tests/test_edge_cases.py::test_advanced_exclude tests/test_edge_cases.py::test_advanced_value_inclide tests/test_edge_cases.py::test_advanced_value_exclude_include tests/test_edge_cases.py::test_values_order tests/test_edge_cases.py::test_inheritance tests/test_edge_cases.py::test_invalid_type tests/test_edge_cases.py::test_valid_string_types[a tests/test_edge_cases.py::test_valid_string_types[some tests/test_edge_cases.py::test_valid_string_types[value2-foobar] tests/test_edge_cases.py::test_valid_string_types[123-123] tests/test_edge_cases.py::test_valid_string_types[123.45-123.45] tests/test_edge_cases.py::test_valid_string_types[value5-12.45] tests/test_edge_cases.py::test_valid_string_types[True-True] tests/test_edge_cases.py::test_valid_string_types[False-False] tests/test_edge_cases.py::test_valid_string_types[a10-a10] tests/test_edge_cases.py::test_valid_string_types[whatever-whatever] tests/test_edge_cases.py::test_invalid_string_types[value0-errors0] tests/test_edge_cases.py::test_invalid_string_types[value1-errors1] tests/test_edge_cases.py::test_inheritance_config tests/test_edge_cases.py::test_partial_inheritance_config tests/test_edge_cases.py::test_annotation_inheritance tests/test_edge_cases.py::test_string_none tests/test_edge_cases.py::test_alias_camel_case tests/test_edge_cases.py::test_get_field_info_inherit tests/test_edge_cases.py::test_return_errors_ok tests/test_edge_cases.py::test_return_errors_error tests/test_edge_cases.py::test_optional_required tests/test_edge_cases.py::test_invalid_validator tests/test_edge_cases.py::test_unable_to_infer tests/test_edge_cases.py::test_multiple_errors tests/test_edge_cases.py::test_pop_by_alias tests/test_edge_cases.py::test_validate_all tests/test_edge_cases.py::test_force_extra tests/test_edge_cases.py::test_illegal_extra_value tests/test_edge_cases.py::test_multiple_inheritance_config tests/test_edge_cases.py::test_submodel_different_type tests/test_edge_cases.py::test_self tests/test_edge_cases.py::test_self_recursive[BaseModel] tests/test_edge_cases.py::test_self_recursive[BaseSettings] tests/test_edge_cases.py::test_nested_init[BaseModel] tests/test_edge_cases.py::test_nested_init[BaseSettings] tests/test_edge_cases.py::test_values_attr_deprecation tests/test_edge_cases.py::test_init_inspection tests/test_edge_cases.py::test_type_on_annotation tests/test_edge_cases.py::test_assign_type tests/test_edge_cases.py::test_optional_subfields tests/test_edge_cases.py::test_not_optional_subfields tests/test_edge_cases.py::test_scheme_deprecated tests/test_edge_cases.py::test_population_by_alias tests/test_edge_cases.py::test_fields_deprecated tests/test_edge_cases.py::test_alias_child_precedence tests/test_edge_cases.py::test_alias_generator_parent tests/test_edge_cases.py::test_optional_field_constraints tests/test_edge_cases.py::test_field_str_shape tests/test_edge_cases.py::test_field_type_display[int-int] tests/test_edge_cases.py::test_field_type_display[type_1-Optional[int]] tests/test_edge_cases.py::test_field_type_display[type_2-Union[NoneType, tests/test_edge_cases.py::test_field_type_display[type_3-Union[int, tests/test_edge_cases.py::test_field_type_display[type_4-List[int]] tests/test_edge_cases.py::test_field_type_display[type_5-Tuple[int, tests/test_edge_cases.py::test_field_type_display[type_6-Union[List[int], tests/test_edge_cases.py::test_field_type_display[type_7-List[Tuple[int, tests/test_edge_cases.py::test_field_type_display[type_8-Mapping[int, tests/test_edge_cases.py::test_field_type_display[type_9-Tuple[int, tests/test_edge_cases.py::test_field_type_display[type_10-Optional[List[int]]] tests/test_main.py::test_success tests/test_main.py::test_ultra_simple_missing tests/test_main.py::test_ultra_simple_failed tests/test_main.py::test_comparing tests/test_main.py::test_nullable_strings_success tests/test_main.py::test_nullable_strings_fails tests/test_main.py::test_recursion tests/test_main.py::test_recursion_fails tests/test_main.py::test_not_required tests/test_main.py::test_infer_type tests/test_main.py::test_allow_extra tests/test_main.py::test_forbidden_extra_success tests/test_main.py::test_forbidden_extra_fails tests/test_main.py::test_disallow_mutation tests/test_main.py::test_extra_allowed tests/test_main.py::test_extra_ignored tests/test_main.py::test_set_attr tests/test_main.py::test_set_attr_invalid tests/test_main.py::test_any tests/test_main.py::test_alias tests/test_main.py::test_population_by_field_name tests/test_main.py::test_field_order tests/test_main.py::test_required tests/test_main.py::test_not_immutability tests/test_main.py::test_immutability tests/test_main.py::test_const_validates tests/test_main.py::test_const_uses_default tests/test_main.py::test_const_with_wrong_value tests/test_main.py::test_const_list tests/test_main.py::test_const_list_with_wrong_value tests/test_main.py::test_const_validation_json_serializable tests/test_main.py::test_validating_assignment_pass tests/test_main.py::test_validating_assignment_fail tests/test_main.py::test_enum_values tests/test_main.py::test_enum_raw tests/test_main.py::test_set_tuple_values tests/test_main.py::test_default_copy tests/test_main.py::test_arbitrary_type_allowed_validation_success tests/test_main.py::test_arbitrary_type_allowed_validation_fails tests/test_main.py::test_arbitrary_types_not_allowed tests/test_main.py::test_type_type_validation_success tests/test_main.py::test_type_type_subclass_validation_success tests/test_main.py::test_type_type_validation_fails_for_instance tests/test_main.py::test_type_type_validation_fails_for_basic_type tests/test_main.py::test_annotation_field_name_shadows_attribute tests/test_main.py::test_value_field_name_shadows_attribute tests/test_main.py::test_class_var tests/test_main.py::test_fields_set tests/test_main.py::test_dir_fields tests/test_main.py::test_dict_with_extra_keys tests/test_main.py::test_alias_generator tests/test_main.py::test_alias_generator_with_field_schema tests/test_main.py::test_alias_generator_wrong_type_error tests/test_main.py::test_root tests/test_main.py::test_root_list tests/test_main.py::test_root_failed tests/test_main.py::test_root_undefined_failed tests/test_main.py::test_parse_root_as_mapping tests/test_main.py::test_untouched_types tests/test_main.py::test_custom_types_fail_without_keep_untouched tests/test_main.py::test_model_iteration tests/test_main.py::test_custom_init_subclass_params tests/test_orm_mode.py::test_getdict tests/test_orm_mode.py::test_orm_mode tests/test_orm_mode.py::test_not_orm_mode tests/test_orm_mode.py::test_properties tests/test_orm_mode.py::test_extra_allow tests/test_orm_mode.py::test_extra_forbid tests/test_orm_mode.py::test_root_validator tests/test_orm_mode.py::test_custom_getter_dict tests/test_orm_mode.py::test_custom_getter_dict_derived_model_class