diff --git a/CHANGELOG.md b/CHANGELOG.md
index bdbf18fd4369d888ea0607552d08891212c1a88a..5715081c5c3ee7ec8b495bc53b7d7772e272ddc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1 +1,5 @@
 # v2.14.23
+
+* added `write_json_to_file`
+* added `read_json_from_file`
+* added `write_json_to_file_if_different`
diff --git a/docs/json.rst b/docs/json.rst
index 904496bb66475b8568f0d8f9a7c53d453277fd40..3ef1e4858bc32a7c52bbd905ddf4ffc6102b2e82 100644
--- a/docs/json.rst
+++ b/docs/json.rst
@@ -21,3 +21,11 @@ This will serialize unknown objects in the following way.
 First, **__dict__** will be extracted out of this object. The dictionary
 will be constructed in such a way, that for each key in this **__dict__**,
 it's value's **repr** will be assigned.
+
+.. autofunction:: satella.json.read_json_from_file
+
+.. autofunction:: satella.json.write_json_to_file
+
+.. autofunction:: satella.json.write_json_to_file_if_different
+
+
diff --git a/satella/__init__.py b/satella/__init__.py
index 8bce66746645d2755a6ed9293f4935839c96a536..702d0b12c8b6dbd385e2b9a9af9777a7f59d52b0 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.14.23_a1'
+__version__ = '2.14.23_a2'
diff --git a/satella/json.py b/satella/json.py
index 77efdbdd1c18874cc6b4ccb7e2b779f31ddbfbcd..b8704306c55c3ee44e4613f22c7df8496d9eb3d1 100644
--- a/satella/json.py
+++ b/satella/json.py
@@ -1,8 +1,13 @@
 import json
 import typing as tp
 from abc import ABCMeta, abstractmethod
+try:
+    import ujson
+except ImportError:
+    pass
 
-__all__ = ['JSONEncoder', 'JSONAble', 'json_encode']
+__all__ = ['JSONEncoder', 'JSONAble', 'json_encode', 'read_json_from_file',
+           'write_json_to_file', 'write_json_to_file_if_different']
 
 Jsonable = tp.TypeVar('Jsonable', list, dict, str, int, float, None)
 
@@ -39,3 +44,61 @@ def json_encode(x: tp.Any) -> str:
     :param x: object to convert
     """
     return JSONEncoder().encode(x)
+
+
+def write_json_to_file(path: str, value: JSONAble) -> None:
+    """
+    Write out a JSON to a file as UTF-8 encoded plain text.
+
+    :param path: path to the file
+    :param value: JSON-able content
+    """
+    if isinstance(value, JSONAble):
+        value = value.to_json()
+    with open(path, 'w') as f_out:
+        try:
+            ujson.dump(value, f_out)
+        except NameError:
+            json.dump(value, f_out)
+
+
+def write_json_to_file_if_different(path: str, value: JSONAble) -> bool:
+    """
+    Read JSON from a file. Write out a JSON to a file if it's value is different,
+    as UTF-8 encoded plain text.
+
+    :param path: path to the file
+    :param value: JSON-able content
+    :return: whether the write actually happened
+    """
+    if isinstance(value, JSONAble):
+        value = value.to_json()
+    try:
+        val = read_json_from_file(path)
+        if val != value:
+            write_json_to_file(path, value)
+            return True
+        return False
+    except (OSError, ValueError):
+        write_json_to_file(path, value)
+        return True
+
+
+def read_json_from_file(path: str) -> JSONAble:
+    """
+    Load a JSON from a provided file, as UTF-8 encoded plain text.
+
+    :param path: path to the file
+    :return: JSON content
+    :raises ValueError: the file contained an invalid JSON
+    :raises OSError: the file was not readable or did not exist
+    """
+    try:
+        with open(path, 'r') as f_in:
+            return ujson.load(f_in)
+    except NameError:
+        with open(path, 'r') as f_in:
+            try:
+                return json.load(f_in)
+            except json.decoder.JSONDecodeError as e:
+                raise ValueError(str(e))
diff --git a/setup.py b/setup.py
index 729eeac1c4e8084a887043a2019d2ef670c5881c..657a930f18aa974434c602bb20e55ad27d1047d8 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ setup(keywords=['ha', 'high availability', 'scalable', 'scalability', 'server',
             'HTTPJSONSource': ['requests'],
             'YAMLSource': ['pyyaml'],
             'TOMLSource': ['toml'],
-            'FasterJSONSource': ['ujson'],
+            'FasterJSON': ['ujson'],
             'cassandra': ['cassandra-driver'],
             'opentracing': ['opentracing']
       }
diff --git a/tests/test_json.py b/tests/test_json.py
index f1f120c0ac080a207c35cf8ecb50a0a27ce9e818..1966d3c5f33e56be3bb45cfc6a8615f60b6f2e56 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -2,11 +2,25 @@ import json
 import typing as tp
 import unittest
 
-from satella.json import JSONAble, json_encode
+from satella.json import JSONAble, json_encode, read_json_from_file, write_json_to_file, \
+write_json_to_file_if_different
 
 
 class TestJson(unittest.TestCase):
 
+    def test_write_json_to_file_if_different(self):
+        d = {'test': 4}
+        self.assertTrue(write_json_to_file_if_different('test2.json', d))
+        self.assertFalse(write_json_to_file_if_different('test2.json', d))
+        d = {'test': 5}
+        self.assertTrue(write_json_to_file_if_different('test2.json', d))
+        self.assertFalse(write_json_to_file_if_different('test2.json', d))
+
+    def test_load_json_from_file(self):
+        d = {'test': 2}
+        write_json_to_file('test.json', d)
+        self.assertEqual(read_json_from_file('test.json'), d)
+
     def test_jsonable_objects(self):
         class MyClass(JSONAble):
             def to_json(self) -> tp.Union[list, dict, str, int, float, None]: