From c3c0d1b0d85ecbcb4032479149197d58f564c78c Mon Sep 17 00:00:00 2001
From: Pierre Gronlier <pierre@gronlier.fr>
Date: Mon, 13 Jan 2025 15:56:49 +0100
Subject: [PATCH] feat: vendor jsonasobj2

---
 Makefile                                      |   2 +
 README.md                                     |  10 +
 linkml_runtime/dumpers/json_dumper.py         |   2 +-
 linkml_runtime/linkml_model/annotations.py    |   2 +-
 linkml_runtime/linkml_model/datasets.py       |   2 +-
 linkml_runtime/linkml_model/extensions.py     |   2 +-
 linkml_runtime/linkml_model/mappings.py       |   2 +-
 linkml_runtime/linkml_model/meta.py           |   2 +-
 linkml_runtime/linkml_model/types.py          |   2 +-
 linkml_runtime/linkml_model/units.py          |   2 +-
 linkml_runtime/linkml_model/validation.py     |   2 +-
 linkml_runtime/loaders/loader_root.py         |   4 +-
 .../processing/validation_datamodel.py        |   2 +-
 linkml_runtime/utils/context_utils.py         |   2 +-
 linkml_runtime/utils/formatutils.py           |   2 +-
 linkml_runtime/utils/inference_utils.py       |   2 +-
 linkml_runtime/utils/jsonasobj2/__init__.py   |   6 +
 linkml_runtime/utils/jsonasobj2/_jsonobj.py   | 313 ++++++++++++++++++
 .../utils/jsonasobj2/extendednamespace.py     |  49 +++
 linkml_runtime/utils/yamlutils.py             |   4 +-
 pyproject.toml                                |   1 -
 tests/support/filters.py                      |   2 +-
 tests/test_index/model/container_test.py      |   2 +-
 tests/test_issues/input/issue_355.py          |   2 +-
 tests/test_issues/models/linkml_issue_576.py  |   2 +-
 tests/test_issues/models/model_817.py         |   2 +-
 tests/test_issues/test_issue_355.py           |   2 +-
 .../models/books_normalized.py                |   2 +-
 .../test_loaders_dumpers/models/enum_model.py |   2 +-
 .../models/node_object.py                     |   2 +-
 .../test_loaders_dumpers/models/personinfo.py |   2 +-
 .../models/personinfo_test_issue_429.py       |   2 +-
 .../models/phenopackets.py                    |   2 +-
 .../test_csv_tsv_loader_dumper.py             |   2 +-
 tests/test_utils/model/inference_example.py   |   2 +-
 tests/test_utils/test_context_utils.py        |   2 +-
 tests/test_utils/test_formatutils.py          |   2 +-
 .../test_utils/test_inlined_as_dict_forms.py  |   2 +-
 .../test_utils/test_inlined_as_list_forms.py  |   2 +-
 tests/test_utils/test_metamodelcore.py        |   2 +-
 tests/test_utils/test_schemaview.py           |   2 +-
 tests/test_utils/test_walker_utils.py         |   2 +-
 42 files changed, 418 insertions(+), 39 deletions(-)
 create mode 100644 linkml_runtime/utils/jsonasobj2/__init__.py
 create mode 100644 linkml_runtime/utils/jsonasobj2/_jsonobj.py
 create mode 100644 linkml_runtime/utils/jsonasobj2/extendednamespace.py

diff --git a/Makefile b/Makefile
index a7d4a8e2..a19bcc20 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,7 @@ MODEL_DIR = ../linkml-model/linkml_model/
 
 update_model:
 	cp -pr $(MODEL_DIR)/* linkml_runtime/linkml_model
+	sed -i 's/from jsonasobj2/from linkml_runtime.utils.jsonasobj2/g' linkml_runtime/linkml_model/*.py
 
 test:
 	poetry run pytest
@@ -16,3 +17,4 @@ test:
 # temporary measure until linkml-model is synced
 linkml_runtime/processing/validation_datamodel.py: linkml_runtime/processing/validation_datamodel.yaml
 	gen-python $< > $@.tmp && mv $@.tmp $@
+	sed -i 's/from jsonasobj2/from linkml_runtime.utils.jsonasobj2/g' linkml_runtime/processing/validation_datamodel.py
diff --git a/README.md b/README.md
index 4020b7c9..65dd747f 100644
--- a/README.md
+++ b/README.md
@@ -29,3 +29,13 @@ It also includes the [SchemaView](https://linkml.io/linkml/developers/manipulati
 ## Notebooks
 
 See the [notebooks](https://github.com/linkml/linkml-runtime/tree/main/notebooks) folder for examples
+
+## Development
+
+### Measure tests code coverage
+
+```shell
+poetry add pytest-coverage
+poetry run coverage run -m pytest # run the existing tests, measuring coverage
+poetry run coverage html # to generate an HTML, browsable report
+```
diff --git a/linkml_runtime/dumpers/json_dumper.py b/linkml_runtime/dumpers/json_dumper.py
index 45837f05..20997ad1 100644
--- a/linkml_runtime/dumpers/json_dumper.py
+++ b/linkml_runtime/dumpers/json_dumper.py
@@ -10,7 +10,7 @@
 from linkml_runtime.utils.context_utils import CONTEXTS_PARAM_TYPE
 from linkml_runtime.utils.formatutils import remove_empty_items
 from linkml_runtime.utils.yamlutils import YAMLRoot, as_json_object
-from jsonasobj2 import JsonObj
+from linkml_runtime.utils.jsonasobj2 import JsonObj
 
 
 class JSONDumper(Dumper):
diff --git a/linkml_runtime/linkml_model/annotations.py b/linkml_runtime/linkml_model/annotations.py
index 9b229b22..1c90198c 100644
--- a/linkml_runtime/linkml_model/annotations.py
+++ b/linkml_runtime/linkml_model/annotations.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/datasets.py b/linkml_runtime/linkml_model/datasets.py
index e8bb1521..6e1b5fbe 100644
--- a/linkml_runtime/linkml_model/datasets.py
+++ b/linkml_runtime/linkml_model/datasets.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/extensions.py b/linkml_runtime/linkml_model/extensions.py
index 3fe8b661..fc23df2a 100644
--- a/linkml_runtime/linkml_model/extensions.py
+++ b/linkml_runtime/linkml_model/extensions.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/mappings.py b/linkml_runtime/linkml_model/mappings.py
index d6135152..d94bec42 100644
--- a/linkml_runtime/linkml_model/mappings.py
+++ b/linkml_runtime/linkml_model/mappings.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/meta.py b/linkml_runtime/linkml_model/meta.py
index fee7f676..bdcad64c 100644
--- a/linkml_runtime/linkml_model/meta.py
+++ b/linkml_runtime/linkml_model/meta.py
@@ -32,7 +32,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/types.py b/linkml_runtime/linkml_model/types.py
index b7034f6f..bde71483 100644
--- a/linkml_runtime/linkml_model/types.py
+++ b/linkml_runtime/linkml_model/types.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/units.py b/linkml_runtime/linkml_model/units.py
index 88f68a31..f021a925 100644
--- a/linkml_runtime/linkml_model/units.py
+++ b/linkml_runtime/linkml_model/units.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/linkml_model/validation.py b/linkml_runtime/linkml_model/validation.py
index 222f1511..dd60cb1c 100644
--- a/linkml_runtime/linkml_model/validation.py
+++ b/linkml_runtime/linkml_model/validation.py
@@ -8,7 +8,7 @@
 
 import dataclasses
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/linkml_runtime/loaders/loader_root.py b/linkml_runtime/loaders/loader_root.py
index e690c4b3..d695b221 100644
--- a/linkml_runtime/loaders/loader_root.py
+++ b/linkml_runtime/loaders/loader_root.py
@@ -4,7 +4,7 @@
 
 from pydantic import BaseModel
 from hbreader import FileInfo, hbread
-from jsonasobj2 import as_dict, JsonObj
+from linkml_runtime.utils.jsonasobj2 import as_dict, JsonObj
 
 from linkml_runtime.utils.yamlutils import YAMLRoot
 from linkml_runtime import URI_TO_LOCAL
@@ -168,4 +168,4 @@ def _read_source(self,
         else:
             data = source
 
-        return data
\ No newline at end of file
+        return data
diff --git a/linkml_runtime/processing/validation_datamodel.py b/linkml_runtime/processing/validation_datamodel.py
index 035835c3..71a27eee 100644
--- a/linkml_runtime/processing/validation_datamodel.py
+++ b/linkml_runtime/processing/validation_datamodel.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import (
diff --git a/linkml_runtime/utils/context_utils.py b/linkml_runtime/utils/context_utils.py
index 1d28fe1b..25d683cf 100644
--- a/linkml_runtime/utils/context_utils.py
+++ b/linkml_runtime/utils/context_utils.py
@@ -4,7 +4,7 @@
 from typing import Optional, Union, List, Any, Dict, Callable
 
 import yaml
-from jsonasobj2 import JsonObj, loads
+from linkml_runtime.utils.jsonasobj2 import JsonObj, loads
 
 CONTEXT_TYPE = Union[str, dict, JsonObj]
 CONTEXTS_PARAM_TYPE = Optional[Union[CONTEXT_TYPE, List[CONTEXT_TYPE]]]
diff --git a/linkml_runtime/utils/formatutils.py b/linkml_runtime/utils/formatutils.py
index da77ba73..d871e833 100644
--- a/linkml_runtime/utils/formatutils.py
+++ b/linkml_runtime/utils/formatutils.py
@@ -3,7 +3,7 @@
 from numbers import Number
 from typing import List, Any, Union
 
-from jsonasobj2 import JsonObj, as_dict, is_list, is_dict, items, as_json_obj
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict, is_list, is_dict, items, as_json_obj
 
 ws_pattern = re.compile(r'\s+')
 us_pattern = re.compile(r'_+')
diff --git a/linkml_runtime/utils/inference_utils.py b/linkml_runtime/utils/inference_utils.py
index ea554529..f1a43cb7 100644
--- a/linkml_runtime/utils/inference_utils.py
+++ b/linkml_runtime/utils/inference_utils.py
@@ -2,7 +2,7 @@
 from dataclasses import field, dataclass
 from enum import Enum
 from typing import Union, Optional, Any, Dict, Callable
-from jsonasobj2 import JsonObj, items
+from linkml_runtime.utils.jsonasobj2 import JsonObj, items
 
 from linkml_runtime import SchemaView
 from linkml_runtime.linkml_model import SlotDefinitionName, PermissibleValue, ClassDefinitionName
diff --git a/linkml_runtime/utils/jsonasobj2/__init__.py b/linkml_runtime/utils/jsonasobj2/__init__.py
new file mode 100644
index 00000000..a05492df
--- /dev/null
+++ b/linkml_runtime/utils/jsonasobj2/__init__.py
@@ -0,0 +1,6 @@
+from ._jsonobj import JsonObj, as_dict, as_json, as_json_obj, get, items, loads, load, setdefault, \
+    keys, items, values, JsonTypes, JsonObjTypes, is_dict, is_list
+from .extendednamespace import ExtendedNamespace
+
+__all__ = ['JsonObj', 'ExtendedNamespace', 'is_dict', 'is_list', 'as_dict', 'as_json', 'as_json_obj', 'get', 'items',
+           'load', 'loads', 'setdefault', 'keys', 'values', 'JsonTypes', 'JsonObjTypes']
diff --git a/linkml_runtime/utils/jsonasobj2/_jsonobj.py b/linkml_runtime/utils/jsonasobj2/_jsonobj.py
new file mode 100644
index 00000000..1df7d0ca
--- /dev/null
+++ b/linkml_runtime/utils/jsonasobj2/_jsonobj.py
@@ -0,0 +1,313 @@
+import json
+from typing import Union, List, Dict, Tuple, Optional, Callable, Any, Iterator
+from hbreader import hbread
+
+from .extendednamespace import ExtendedNamespace
+
+# Possible types in the JsonObj representation
+JsonObjTypes = Union["JsonObj", List["JsonObjTypes"], str, bool, int, float, None]
+
+# Types in the pure JSON representation
+JsonTypes = Union[Dict[str, "JsonTypes"], List["JsonTypes"], str, bool, int, float, None]
+
+# Control variables -- note that subclasses can add to this list
+hide = ['_if_missing', '_root']
+
+
+class JsonObj(ExtendedNamespace):
+    """ A namespace/dictionary representation of a JSON object. Any name in a JSON object that is a valid python
+    identifier is represented as a first-class member of the objects.  JSON identifiers that begin with "_" are
+    disallowed in this implementation.
+    """
+    # Set this class variable to False if recursive construction is absolutely necessare (see: test_issue13.py for
+    # details
+    _idempotent = True
+
+    def __new__(cls, *args, _if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None, **kwargs):
+        """ Construct a JsonObj from set of keyword/value pairs
+
+        :param list_or_dict: A list or dictionary that can be used to construct the object
+        :param _if_missing: Function to call if attempt is made to access an undefined value.  Function takes JsonObj
+        instance and parameter as input and returns a tuple -- handled (y or n) and result.  If handled is 'n' inline
+        processing proceeds.
+        :param kwargs: A dictionary as an alternative constructor.
+        """
+        # This makes JsonObj idempotent
+        if cls._idempotent and args and isinstance(args[0], JsonObj):
+            # If we're being called with a single argument
+            if not kwargs and not args[1:] and\
+                    (not _if_missing or _if_missing == args[0]._if_missing) and cls == type(args[0]):
+                return args[0]
+        obj = super(ExtendedNamespace, cls).__new__(cls)
+        return obj
+
+    def __init__(self, *args, _if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None, **kwargs):
+        """ Construct a JsonObj from set of keyword/value pairs
+
+        :param list_or_dict: A list or dictionary that can be used to construct the object
+        :param _if_missing: Function to call if attempt is made to access an undefined value.  Function takes JsonObj
+        instance and parameter as input and returns a tuple -- handled (y or n) and result.  If handled is 'n' inline
+        processing proceeds.
+        :param kwargs: A dictionary as an alternative constructor.
+        """
+        if args and isinstance(args[0], JsonObj) and not kwargs and not args[1:] and type(self)._idempotent and \
+                    (not _if_missing or _if_missing == args[0]._if_missing) and type(self) == type(args[0]):
+            return
+
+        if _if_missing and _if_missing != self._if_missing:
+            self._if_missing = _if_missing
+        if args:
+            if kwargs:
+                raise TypeError("Constructor can't have both a single item and a dict")
+            if isinstance(args[0], JsonObj):
+                pass
+            elif isinstance(args[0], dict):
+                self._init_from_dict(args[0])
+            elif isinstance(args[0], list):
+                ExtendedNamespace.__init__(self,
+                                           _root=[JsonObj(e) if isinstance(e, (dict, list)) else
+                                                  e for e in args[0]])
+            else:
+                raise TypeError("JSON Object can only be a list or dictionary")
+        else:
+            self._init_from_dict(kwargs)
+
+    @staticmethod
+    def _if_missing(obj: "JsonObj", item: str) -> Tuple[bool, Any]:
+        return False, None
+
+    def _init_from_dict(self, d: Union[dict, "JsonObj"]) -> None:
+        """ Construct a JsonObj from a dictionary or another JsonObj """
+        if not isinstance(d, JsonObj):
+            ExtendedNamespace.__init__(self, _if_missing=self._if_missing,
+                                       **{str(k): JsonObj(v) if isinstance(v, dict) else v for k, v in d.items()})
+
+    def _hide_list(self):
+        return self._root if '_root' in self else self
+
+    def __str__(self) -> str:
+        return str(self._root) if '_root' in self else super().__str__()
+
+    # ===================================================
+    # JSON Serializer method
+    # ===================================================
+    @staticmethod
+    def _static_default(obj, filtr: Callable[[Dict], Dict] = lambda e: e):
+        """ return a serialized version of obj or raise a TypeError.  Used by the JSON serializer
+
+        :param obj:
+        :param filtr: dictionary filter
+        :return: Serialized version of obj
+        """
+        return filtr(obj._as_dict) if isinstance(obj, JsonObj) else json.JSONDecoder().decode(obj)
+
+    def _default(self, obj, filtr: Callable[[Dict], Dict] = lambda e: e):
+        """ This default method is here to allow inheriting classes to override it when needed """
+        return JsonObj._static_default(obj, filtr)
+
+    # ===================================================
+    # Underscore equivalent of useful dictionary functions
+    # ===================================================
+    def _get(self, item: str, default: JsonObjTypes = None) -> JsonObjTypes:
+        """ Equivalent to dictionary get function w/o polluting namespace """
+        return self[item] if item in self else default
+
+    def _setdefault(self, k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes:
+        """ Equivalent of dictionary setdefault without messing in namespace """
+        if k not in self:
+            self[k] = JsonObj(_if_missing=self._if_missing, **value) if isinstance(value, dict) else value
+        return self[k]
+
+    def _keys(self) -> List[str]:
+        """ Return all non-hidden keys """
+        for k in self._hide_list().__dict__.keys():
+            if k not in hide:
+                yield k
+
+    def _items(self) -> List[Tuple[str, JsonObjTypes]]:
+        """ Return all non-hidden items """
+        for k, v in self._hide_list().__dict__.items():
+            if k not in hide:
+                yield k, v
+
+    def _values(self) -> List[JsonObjTypes]:
+        """ Return all non hidden values """
+        for _, v in self._items():
+            yield v
+
+    # ===================================================
+    # Various converters -- use exposed methods in place of underscores
+    # ===================================================
+    def _as_json_obj(self) -> JsonTypes:
+        """ Return self as pure json """
+        return json.loads(self._as_json_dumps())
+
+    def __getitem__(self, item):
+        if '_root' in self:
+            return self._root[item]
+        else:
+            found, val = self._if_missing(self, item)
+            if found:
+                return val
+            else:
+                return super().__getitem__(item)
+
+    def __getattr__(self, item):
+        found, val = self._if_missing(self, item)
+        if found:
+            return val
+        else:
+            return super().__getattribute__(item)
+
+    def __setattr__(self, key, value):
+        super().__setattr__(key, JsonObj(value) if isinstance(value, dict) else value)
+
+    def __bool__(self):
+        if '_root' in self:
+            return bool(self._root)
+        else:
+            return bool(any(self._keys()))
+
+    @property
+    def _as_json(self) -> str:
+        """ Convert a JsonObj into straight json text
+
+        :return: JSON formatted str
+        """
+        return json.dumps(self, default=self._default)
+
+    def _as_json_dumps(self, indent: str = '   ', filtr: Callable[[Dict], Dict] = None, **kwargs) -> str:
+        """ Convert to a stringified json object.
+
+        This is the same as _as_json with the exception that it isn't
+        a property, meaning that we can actually pass arguments...
+        :param indent: indent argument to dumps
+        :param filtr: dictionary filter
+        :param kwargs: other arguments for dumps
+        :return: JSON formatted string
+        """
+        return json.dumps(self,
+                          default=lambda obj: self._default(obj, filtr) if filtr else self._default(obj),
+                          indent=indent,
+                          **kwargs)
+
+    @property
+    def _as_dict(self) -> Dict[str, JsonTypes]:
+        """ Convert a JsonObj into a straight dictionary
+
+        :return: dictionary that cooresponds to the json object
+        """
+        return as_dict(self)
+
+
+def loads(s: str, **kwargs) -> JsonObj:
+    """ Convert a json_str into a JsonObj
+
+    :param s: a str instance containing a JSON document
+    :param kwargs: arguments see: json.load for details
+    :return: JsonObj representing the json string
+    """
+    if isinstance(s, (bytes, bytearray)):
+        s = s.decode(json.detect_encoding(s), 'surrogatepass')
+
+    return JsonObj(json.loads(s, object_hook=lambda pairs: JsonObj(pairs), **kwargs))
+
+
+def load(source, **kwargs) -> JsonObj:
+    """ Deserialize a JSON source.
+
+    :param source: a URI, File name or a .read()-supporting file-like object containing a JSON document
+    :param kwargs: arguments. see: json.load for details
+    :return: JsonObj representing fp
+    """
+    return loads(hbread(source, accept_header="application/json, text/json;q=0.9"), **kwargs)
+
+
+def is_dict(obj: Union[JsonObj, Any]) -> bool:
+    """
+    Determine whether obj is a dictionary or a JsonObj containing a dictionary
+    """
+    return isinstance(obj, dict) or (isinstance(obj, JsonObj) and not isinstance(obj._hide_list(), list))
+
+
+def is_list(obj: Union[JsonObj, Any]) -> bool:
+    """
+    Determine whether obj is a dictionary or a JsonObj containing a dictionary
+    """
+    return isinstance(obj, list) or (isinstance(obj, JsonObj) and isinstance(obj._hide_list(), list))
+
+
+def as_dict(obj: Union[JsonObj, List]) -> Union[List, Dict[str, JsonTypes]]:
+    """ Convert a JsonObj into a straight dictionary or list
+
+    :param obj: pseudo 'self'
+    :return: dictionary that cooresponds to the json object
+    """
+    if isinstance(obj, (list, JsonObj)):
+        return \
+            {k: as_dict(v) if isinstance(v, (list, dict, JsonObj)) else v
+             for k, v in items(obj)} if is_dict(obj) else \
+            [as_dict(e) if isinstance(e, (list, dict, JsonObj)) else
+             e for e in (obj._hide_list() if isinstance(obj, JsonObj) else obj)]
+    else:
+        return obj
+
+def as_json(obj: Union[Dict, JsonObj, List], indent: Optional[str] = '   ',
+            filtr: Callable[[Dict], Dict] = None, **kwargs) -> str:
+    """ Convert obj to json string representation.
+
+        :param obj: pseudo 'self'
+        :param indent: indent argument to dumps
+        :param filtr: filter to remove unwanted elements
+        :param kwargs: other arguments for dumps
+        :return: JSON formatted string
+       """
+    if isinstance(obj, JsonObj) and '_root' in obj:
+        obj = obj._root
+    default_processor = \
+        obj._default if isinstance(obj, JsonObj) else JsonObj._static_default
+    return obj._as_json_dumps(indent,
+                              filtr=filtr,
+                              **kwargs) if isinstance(obj, JsonObj) else \
+        json.dumps(obj,
+                   default=lambda o: default_processor(o, filtr) if filtr else default_processor(o),
+                   indent=indent,
+                   *kwargs)
+
+
+def as_json_obj(obj: Union[Dict, JsonObj, List]) -> JsonTypes:
+    """ Return obj as pure python json (vs. JsonObj)
+        :param obj: pseudo 'self'
+        :return: Pure python json image
+    """
+    if isinstance(obj, JsonObj):
+        obj = obj._hide_list()
+    return [as_json_obj(e) for e in obj] if isinstance(obj, list) else\
+        obj._as_json_obj() if isinstance(obj, JsonObj) else obj
+
+
+def get(obj: Union[Dict, JsonObj], item: str, default: JsonObjTypes = None) -> JsonObjTypes:
+    """ Dictionary get routine """
+    return obj._get(item, default) if isinstance(obj, JsonObj) else obj.get(item, default)
+
+
+def setdefault(obj: Union[Dict, JsonObj], k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes:
+    """ Dictionary setdefault routine """
+    return obj._setdefault(k, value) if isinstance(obj, JsonObj) else obj.setdefault(k, value)
+
+
+def keys(obj: Union[Dict, JsonObj]) -> Iterator[str]:
+    """ same as dict keys() without polluting the namespace """
+    return obj._keys() if isinstance(obj, JsonObj) else obj.keys()
+
+
+def items(obj: Union[Dict, JsonObj]) -> Iterator[Tuple[str, JsonObjTypes]]:
+    """ Same as dict items() except that the values are JsonObjs instead of vanilla dictionaries
+    :return:
+    """
+    return obj._items() if isinstance(obj, JsonObj) else obj.items()
+
+
+def values(obj: Union[Dict, JsonObj]) -> Iterator[JsonObjTypes]:
+    """ Same as dict values() except that the values are JsonObjs """
+    return obj._values() if isinstance(obj, JsonObj) else obj.values()
diff --git a/linkml_runtime/utils/jsonasobj2/extendednamespace.py b/linkml_runtime/utils/jsonasobj2/extendednamespace.py
new file mode 100644
index 00000000..27359a20
--- /dev/null
+++ b/linkml_runtime/utils/jsonasobj2/extendednamespace.py
@@ -0,0 +1,49 @@
+
+
+class ExtendedNamespace:
+    """ A combination of a namespace and a dictionary.  This allows direct access to python properties plus
+    dictionary access to everything.
+    """
+    def __init__(self, **kwargs):
+        for name in kwargs:
+            setattr(self, name, kwargs[name])
+
+    def __eq__(self, other):
+        if not isinstance(other, ExtendedNamespace):
+            return NotImplemented
+        return vars(self) == vars(other)
+
+    def __contains__(self, key):
+        return key in self.__dict__
+
+    def __getitem__(self, item):
+        return self.__dict__[item]
+
+    def __setitem__(self, key, item):
+        self.__dict__[key] = item
+
+    def __delitem__(self, key):
+        del self.__dict__[key]
+
+    def __iter__(self):
+        return self.__dict__.__iter__()
+
+    def __len__(self):
+        return len(self.__dict__)
+
+    def __repr__(self):
+        type_name = type(self).__name__
+        arg_strings = []
+        star_args = {}
+        for name, value in list(self.__dict__.items()):
+            if not name.startswith('_'):
+                if name.isidentifier():
+                    arg_strings.append('%s=%r' % (name, value))
+                else:
+                    star_args[name] = value
+        if star_args:
+            arg_strings.append('**%s' % repr(star_args))
+        return '%s(%s)' % (type_name, ', '.join(arg_strings))
+
+    def _get(self, key, default=None):
+        return self.__dict__.get(key, default)
diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py
index 219cb135..0fa2bb94 100644
--- a/linkml_runtime/utils/yamlutils.py
+++ b/linkml_runtime/utils/yamlutils.py
@@ -7,8 +7,8 @@
 
 import yaml
 from deprecated.classic import deprecated
-from jsonasobj2 import JsonObj, as_json, as_dict, JsonObjTypes, items
-import jsonasobj2
+from .jsonasobj2 import JsonObj, as_json, as_dict, JsonObjTypes, items
+from . import jsonasobj2
 from rdflib import Graph, URIRef
 from yaml.constructor import ConstructorError
 
diff --git a/pyproject.toml b/pyproject.toml
index b50cba65..620fa726 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -52,7 +52,6 @@ click = "*"
 deprecated = "*"
 hbreader = "*"
 json-flattener = ">=0.1.9"
-jsonasobj2 = "==1.*,>=1.0.0,>=1.0.4"
 jsonschema = ">=3.2.0"
 prefixcommons = ">=0.1.12"
 pyyaml = "*"
diff --git a/tests/support/filters.py b/tests/support/filters.py
index fa7967db..70949139 100644
--- a/tests/support/filters.py
+++ b/tests/support/filters.py
@@ -3,7 +3,7 @@
 import re
 from json import loads
 
-from jsonasobj2 import as_json
+from linkml_runtime.utils.jsonasobj2 import as_json
 
 def ldcontext_metadata_filter(s: str) -> str:
     """
diff --git a/tests/test_index/model/container_test.py b/tests/test_index/model/container_test.py
index 23d1f84a..a7de3797 100644
--- a/tests/test_index/model/container_test.py
+++ b/tests/test_index/model/container_test.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_issues/input/issue_355.py b/tests/test_issues/input/issue_355.py
index b5117559..e9a825ee 100644
--- a/tests/test_issues/input/issue_355.py
+++ b/tests/test_issues/input/issue_355.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj
+from linkml_runtime.utils.jsonasobj2 import JsonObj
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/tests/test_issues/models/linkml_issue_576.py b/tests/test_issues/models/linkml_issue_576.py
index 81fb80c7..c689c68c 100644
--- a/tests/test_issues/models/linkml_issue_576.py
+++ b/tests/test_issues/models/linkml_issue_576.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_issues/models/model_817.py b/tests/test_issues/models/model_817.py
index ce6ac68f..2cf2a721 100644
--- a/tests/test_issues/models/model_817.py
+++ b/tests/test_issues/models/model_817.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_issues/test_issue_355.py b/tests/test_issues/test_issue_355.py
index a1dc7872..ae4a118c 100644
--- a/tests/test_issues/test_issue_355.py
+++ b/tests/test_issues/test_issue_355.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj
+from linkml_runtime.utils.jsonasobj2 import JsonObj
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 
diff --git a/tests/test_loaders_dumpers/models/books_normalized.py b/tests/test_loaders_dumpers/models/books_normalized.py
index b06d6547..63726d73 100644
--- a/tests/test_loaders_dumpers/models/books_normalized.py
+++ b/tests/test_loaders_dumpers/models/books_normalized.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_loaders_dumpers/models/enum_model.py b/tests/test_loaders_dumpers/models/enum_model.py
index 141e6874..b8e90517 100644
--- a/tests/test_loaders_dumpers/models/enum_model.py
+++ b/tests/test_loaders_dumpers/models/enum_model.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_loaders_dumpers/models/node_object.py b/tests/test_loaders_dumpers/models/node_object.py
index 4ebaa2b5..7204367b 100644
--- a/tests/test_loaders_dumpers/models/node_object.py
+++ b/tests/test_loaders_dumpers/models/node_object.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_loaders_dumpers/models/personinfo.py b/tests/test_loaders_dumpers/models/personinfo.py
index a061168e..a50521e4 100644
--- a/tests/test_loaders_dumpers/models/personinfo.py
+++ b/tests/test_loaders_dumpers/models/personinfo.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py b/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py
index fed09fa2..8da9e7c6 100644
--- a/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py
+++ b/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_loaders_dumpers/models/phenopackets.py b/tests/test_loaders_dumpers/models/phenopackets.py
index 255a4578..4bf44c8a 100644
--- a/tests/test_loaders_dumpers/models/phenopackets.py
+++ b/tests/test_loaders_dumpers/models/phenopackets.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py b/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py
index 80dc5a5d..202ad62b 100644
--- a/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py
+++ b/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py
@@ -3,7 +3,7 @@
 import json
 import logging
 
-from jsonasobj2 import as_json_obj, JsonObj
+from linkml_runtime.utils.jsonasobj2 import as_json_obj, JsonObj
 
 from linkml_runtime.dumpers import json_dumper, yaml_dumper
 from linkml_runtime.loaders import yaml_loader
diff --git a/tests/test_utils/model/inference_example.py b/tests/test_utils/model/inference_example.py
index 3cbc5d3f..7da374c3 100644
--- a/tests/test_utils/model/inference_example.py
+++ b/tests/test_utils/model/inference_example.py
@@ -9,7 +9,7 @@
 import dataclasses
 import sys
 import re
-from jsonasobj2 import JsonObj, as_dict
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict
 from typing import Optional, List, Union, Dict, ClassVar, Any
 from dataclasses import dataclass
 from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions
diff --git a/tests/test_utils/test_context_utils.py b/tests/test_utils/test_context_utils.py
index 8e8bd2b8..4ad7c52b 100644
--- a/tests/test_utils/test_context_utils.py
+++ b/tests/test_utils/test_context_utils.py
@@ -1,6 +1,6 @@
 import unittest
 
-from jsonasobj2 import JsonObj, loads
+from linkml_runtime.utils.jsonasobj2 import JsonObj, loads
 
 from linkml_runtime.utils.context_utils import merge_contexts
 from tests.test_utils import METAMODEL_CONTEXT_URI, META_BASE_URI
diff --git a/tests/test_utils/test_formatutils.py b/tests/test_utils/test_formatutils.py
index 5107cd3d..49ac58be 100644
--- a/tests/test_utils/test_formatutils.py
+++ b/tests/test_utils/test_formatutils.py
@@ -2,7 +2,7 @@
 import unittest
 from typing import List, Tuple, Any
 
-from jsonasobj2 import JsonObj, as_json
+from linkml_runtime.utils.jsonasobj2 import JsonObj, as_json
 
 from linkml_runtime.utils.formatutils import camelcase, underscore, lcamelcase, be, split_line, wrapped_annotation, \
     is_empty, remove_empty_items, uncamelcase
diff --git a/tests/test_utils/test_inlined_as_dict_forms.py b/tests/test_utils/test_inlined_as_dict_forms.py
index 01aa4ac1..f1dcd971 100644
--- a/tests/test_utils/test_inlined_as_dict_forms.py
+++ b/tests/test_utils/test_inlined_as_dict_forms.py
@@ -1,6 +1,6 @@
 import unittest
 
-from jsonasobj2 import JsonObj
+from linkml_runtime.utils.jsonasobj2 import JsonObj
 
 from tests.test_utils.input.inlined_as_dict import E, EInst
 
diff --git a/tests/test_utils/test_inlined_as_list_forms.py b/tests/test_utils/test_inlined_as_list_forms.py
index 6c606097..9f22f8c4 100644
--- a/tests/test_utils/test_inlined_as_list_forms.py
+++ b/tests/test_utils/test_inlined_as_list_forms.py
@@ -1,6 +1,6 @@
 import unittest
 
-from jsonasobj2 import JsonObj
+from linkml_runtime.utils.jsonasobj2 import JsonObj
 
 from tests.test_utils.input.inlined_as_list import E, EInst
 
diff --git a/tests/test_utils/test_metamodelcore.py b/tests/test_utils/test_metamodelcore.py
index 1bc26739..6cab502f 100644
--- a/tests/test_utils/test_metamodelcore.py
+++ b/tests/test_utils/test_metamodelcore.py
@@ -2,7 +2,7 @@
 import unittest
 from dataclasses import dataclass
 
-from jsonasobj2 import as_json
+from linkml_runtime.utils.jsonasobj2 import as_json
 from rdflib import Literal, XSD, Graph, RDF, Namespace
 
 from linkml_runtime.utils.metamodelcore import NCName, Bool, URIorCURIE, URI, XSDDate, XSDDateTime, XSDTime, Curie, \
diff --git a/tests/test_utils/test_schemaview.py b/tests/test_utils/test_schemaview.py
index c9f28ff8..aa39c3f3 100644
--- a/tests/test_utils/test_schemaview.py
+++ b/tests/test_utils/test_schemaview.py
@@ -3,7 +3,7 @@
 from copy import copy
 from pathlib import Path
 import pytest
-from jsonasobj2 import JsonObj
+from linkml_runtime.utils.jsonasobj2 import JsonObj
 
 from linkml_runtime.dumpers import yaml_dumper
 from linkml_runtime.linkml_model.meta import Example, SchemaDefinition, ClassDefinition, SlotDefinitionName, SlotDefinition, \
diff --git a/tests/test_utils/test_walker_utils.py b/tests/test_utils/test_walker_utils.py
index 195b1100..a613a482 100644
--- a/tests/test_utils/test_walker_utils.py
+++ b/tests/test_utils/test_walker_utils.py
@@ -2,7 +2,7 @@
 import unittest
 from copy import deepcopy
 
-from jsonasobj2 import as_dict
+from linkml_runtime.utils.jsonasobj2 import as_dict
 
 from linkml_runtime.linkml_model import SchemaDefinition, ClassDefinition
 from linkml_runtime.utils.walker_utils import traverse_object_tree