diff --git a/configmanager/__init__.py b/configmanager/__init__.py
index 246eadc..f2d2fcc 100644
--- a/configmanager/__init__.py
+++ b/configmanager/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '1.2.0'
+__version__ = '1.3.0'
 
 from .managers import Config
 from .items import Item
diff --git a/configmanager/managers.py b/configmanager/managers.py
index 5a611d8..a0f6346 100644
--- a/configmanager/managers.py
+++ b/configmanager/managers.py
@@ -49,6 +49,7 @@ class Config(BaseSection):
         instance = super(Config, cls).__new__(cls)
 
         instance._cm__section = None
+        instance._cm__section_alias = None
         instance._cm__configs = collections.OrderedDict()
         instance._cm__configparser_adapter = None
         instance._cm__json_adapter = None
@@ -68,10 +69,41 @@ class Config(BaseSection):
     def __repr__(self):
         return '<{cls} at {id}>'.format(cls=self.__class__.__name__, id=id(self))
 
-    def __contains__(self, item):
-        return item in self._cm__configs
+    def _resolve_config_key(self, key):
+        if isinstance(key, six.string_types):
+            return self._cm__configs[key]
+        elif isinstance(key, (tuple, list)) and len(key) > 0:
+            if len(key) == 1:
+                return self[key[0]]
+            else:
+                return self[key[0]][key[1:]]
+        else:
+            raise TypeError('Expected either a string or a tuple as key, got {!r}'.format(key))
+
+    def __contains__(self, key):
+        try:
+            _ = self._resolve_config_key(key)
+            return True
+        except KeyError:
+            return False
+
+    def __setitem__(self, key, value):
+        if isinstance(key, six.string_types):
+            name = key
+            rest = None
+        elif isinstance(key, (tuple, list)) and len(key) > 0:
+            name = key[0]
+            if len(key) == 1:
+                rest = None
+            else:
+                rest = key[1:]
+        else:
+            raise TypeError('Expected either a string or a tuple as key, got {!r}'.format(key))
+
+        if rest:
+            self[name][rest] = value
+            return
 
-    def __setitem__(self, name, value):
         if is_config_item(value):
             self.cm__add_item(name, value)
         elif isinstance(value, self.__class__):
@@ -85,8 +117,8 @@ class Config(BaseSection):
                 )
             )
 
-    def __getitem__(self, name):
-        return self._cm__configs[name]
+    def __getitem__(self, key):
+        return self._resolve_config_key(key)
 
     def __getattr__(self, name):
         if name in self._cm__configs:
@@ -110,6 +142,15 @@ class Config(BaseSection):
                 )
             )
 
+    def __len__(self):
+        return sum(1 for _ in self.iter_items())
+
+    def __nonzero__(self):
+        return True
+
+    def __bool__(self):
+        return True
+
     def iter_items(self):
         """
         Iterate over all items contained (recursively).
@@ -224,6 +265,17 @@ class Config(BaseSection):
         """
         return self._cm__section
 
+    @property
+    def alias(self):
+        """
+        Returns alias with which this section was added to another or ``None`` if it hasn't been added
+        to any.
+        
+        Returns:
+            (str)
+        """
+        return self._cm__section_alias
+
     def added_to_section(self, alias, section):
         """
         A hook that is called when this section is added to another.
@@ -234,6 +286,7 @@ class Config(BaseSection):
             section (:class:`.Config`): section to which this section has been added
         """
         self._cm__section = section
+        self._cm__section_alias = alias
 
     @property
     def configparser(self):
diff --git a/configmanager/persistence.py b/configmanager/persistence.py
index 2a284f2..9e4e360 100644
--- a/configmanager/persistence.py
+++ b/configmanager/persistence.py
@@ -126,7 +126,7 @@ class JsonAdapter(object):
         else:
             self._config.read_dict(json.load(source), as_defaults=as_defaults)
 
-    def write(self, destination, with_defaults=True):
+    def write(self, destination, with_defaults=False):
         if isinstance(destination, six.string_types):
             with open(destination, 'w') as f:
                 json.dump(self._config.to_dict(with_defaults=with_defaults), f)
diff --git a/requirements.txt b/requirements.txt
index b2945bc..b7fcb9f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@ six
 configparser
 
 pytest
+pytest-random-order
 coverage
 tox
 

len(Config)?
What would you expect -- number of sections or number of items? Probably items.
diff --git a/tests/test_config.py b/tests/test_config.py
index 856120e..562ab1c 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -343,7 +343,6 @@ def test_can_inspect_config_contents(mixed_app_config):
 
     assert 'db' in config
     assert 'dbe' not in config
-    assert ('db',) not in config
 
     assert 'logging' in config
 
@@ -379,4 +378,67 @@ def test_can_have_a_dict_as_a_config_value_if_wrapped_inside_item():
     # value, not the real thing.
     config.aws.value['secret_key'] = 'NEW_SECRET'
 
-    assert config.to_dict()['aws'] == {'access_key': '123', 'secret_key': 'secret'}
\ No newline at end of file
+    assert config.to_dict()['aws'] == {'access_key': '123', 'secret_key': 'secret'}
+
+
+def test_len_of_config_returns_number_of_items_in_it():
+    assert len(Config()) == 0
+
+    assert len(Config({'enabled': True})) == 1
+
+    assert len(Config({'uploads': Config()})) == 0
+    assert len(Config({'uploads': {}})) == 0
+
+    assert len(Config({'uploads': {'enabled': False}})) == 1
+    assert len(Config({'uploads': {'enabled': False, 'threads': 1}})) == 2
+
+    assert len(Config({'uploads': {'enabled': False, 'threads': 0}, 'greeting': 'Hi'})) == 3
+
+
+def test__getitem__handles_paths_to_sections_and_items_and_so_does__contains__():
+    config = Config()
+    with pytest.raises(KeyError):
+        assert not config['uploads', 'enabled']
+    assert ('uploads',) not in config
+    assert ('uploads', 'enabled') not in config
+
+    config.uploads = Config({'enabled': True, 'db': {'user': 'root'}})
+    assert config['uploads', 'enabled'] is config.uploads.enabled
+    assert config['uploads', 'db'] is config.uploads.db
+
+    assert 'uploads' in config
+    assert ('uploads',) in config
+    assert ('uploads', 'enabled') in config
+    assert ('uploads', 'db') in config
+    assert ('uploads', 'db', 'user') in config
+
+    assert config.uploads.db.user.value == 'root'
+
+    config['uploads', 'db', 'user'].set('admin')
+    assert config.uploads.db.user.value == 'admin'
+
+
+def test_can_use__setitem__to_create_new_deep_paths():
+    config = Config()
+    config['uploads'] = Config({'enabled': True})
+
+    with pytest.raises(TypeError):
+        config['uploads', 'threads'] = 5
+
+    config['uploads', 'threads'] = Item(value=5)
+    assert config.uploads.threads.type is int
+
+    config['uploads', 'db'] = Config({'user': 'root'})
+    assert config.uploads.db
+
+
+def test_section_knows_its_alias():
+    config = Config()
+    config.uploads = Config({
+        'enabled': True
+    })
+    assert config.uploads.alias == 'uploads'
+
+    config.uploads.db = Config({'connection': {'user': 'root'}})
+    assert config.uploads.db.alias == 'db'
+    assert config.uploads.db.connection.alias == 'connection'
diff --git a/tests/test_json.py b/tests/test_json.py
index 68c9c6a..0e7530b 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -91,3 +91,24 @@ def test_json_read_and_write(defaults_json_path, user_json_path):
     c1.json.read(user_json_path)
 
     assert c1.to_dict() == c2.to_dict() == c3.to_dict()
+
+
+def test_json_writes_with_defaults_false_by_default(user_json_path):
+    c = Config({'greeting': 'Hello'})
+    c.json.write(user_json_path)
+
+    d = Config()
+    d.json.read(user_json_path, as_defaults=True)
+    assert len(d) == 0
+
+    c.json.write(user_json_path, with_defaults=True)
+
+    d.json.read(user_json_path, as_defaults=True)
+    assert len(d) == 1
+    assert d.greeting.value == 'Hello'
+
+    c.greeting.value = 'Hey!'
+    c.json.write(user_json_path)
+
+    d.json.read(user_json_path, as_defaults=True)
+    assert d.greeting.value == 'Hey!'

pip install -e .
parse_log_pytest
requirements.txt
pytest
3.5
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
-e git+https://github.com/jbasko/configmanager.git@7046d348f0afcf11df79afa3d6ad9fb0f6757605#egg=configmanager
configparser==5.2.0
coverage==6.2
distlib==0.3.9
filelock==3.4.1
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig==1.1.1
packaging==21.3
platformdirs==2.4.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
six==1.17.0
toml==0.10.2
tomli==1.2.3
tox==3.28.0
typing_extensions==4.1.1
virtualenv==20.17.1
zipp==3.6.0

name: configmanager
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - configparser==5.2.0
      - coverage==6.2
      - distlib==0.3.9
      - filelock==3.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - packaging==21.3
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - six==1.17.0
      - toml==0.10.2
      - tomli==1.2.3
      - tox==3.28.0
      - typing-extensions==4.1.1
      - virtualenv==20.17.1
      - zipp==3.6.0
prefix: /opt/conda/envs/configmanager

tests/test_config.py::test_len_of_config_returns_number_of_items_in_it tests/test_config.py::test__getitem__handles_paths_to_sections_and_items_and_so_does__contains__ tests/test_config.py::test_can_use__setitem__to_create_new_deep_paths tests/test_config.py::test_section_knows_its_alias tests/test_json.py::test_json_writes_with_defaults_false_by_default
tests/test_config.py::test_items_are_created_using_cm_create_item_method tests/test_config.py::test_reset_resets_values_to_defaults tests/test_config.py::test_repr_of_config tests/test_config.py::test_assigning_nameless_item_directly_to_config_should_set_its_name tests/test_config.py::test_assigning_item_with_name_directly_to_config_should_preserve_its_name tests/test_config.py::test_item_name_and_alias_must_be_a_string tests/test_config.py::test_section_name_must_be_a_string tests/test_config.py::test_to_dict_should_not_include_items_with_no_usable_value tests/test_config.py::test_read_dict_recursively_loads_values_from_a_dictionary tests/test_config.py::test_read_dict_as_defaults_loads_default_values_from_a_dictionary tests/test_config.py::test_declaration_parser_does_not_modify_config tests/test_config.py::test_allows_iteration_over_all_items tests/test_config.py::test_forbids_accidental_item_overwrite_via_setitem tests/test_config.py::test_allows_iteration_over_sections tests/test_config.py::test_attribute_read_access tests/test_config.py::test_attribute_write_access tests/test_config.py::test_forbids_accidental_item_overwrite_via_setattr tests/test_config.py::test_to_dict tests/test_config.py::test_can_inspect_config_contents tests/test_config.py::test_can_have_a_dict_as_a_config_value_if_wrapped_inside_item tests/test_json.py::test_json_read_and_write
configmanager/persistence.py requirements.txt configmanager/__init__.py configmanager/managers.py
configmanager/persistence.py requirements.txt configmanager/__init__.py configmanager/managers.py