diff --git a/stix2/base.py b/stix2/base.py index 31f5a42..2374bf5 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -1,7 +1,6 @@ """Base classes for type definitions in the STIX2 library.""" import copy -import datetime as dt import re import uuid @@ -18,9 +17,10 @@ from .exceptions import ( ) from .markings import _MarkingsMixin from .markings.utils import validate -from .utils import ( - NOW, PREFIX_21_REGEX, find_property_index, format_datetime, get_timestamp, +from .serialize import ( + STIXJSONEncoder, STIXJSONIncludeOptionalDefaultsEncoder, serialize, ) +from .utils import NOW, PREFIX_21_REGEX, get_timestamp from .versioning import new_version as _new_version from .versioning import revoke as _revoke @@ -29,88 +29,14 @@ try: except ImportError: from collections import Mapping - -__all__ = ['STIXJSONEncoder', '_STIXBase', 'serialize'] +# TODO: Remove STIXJSONEncoder, STIXJSONIncludeOptionalDefaultsEncoder, serialize from __all__ on next major release. +# Kept for backwards compatibility. +__all__ = ['STIXJSONEncoder', 'STIXJSONIncludeOptionalDefaultsEncoder', '_STIXBase', 'serialize'] DEFAULT_ERROR = "{type} must have {property}='{expected}'." SCO_DET_ID_NAMESPACE = uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7") -class STIXJSONEncoder(json.JSONEncoder): - """Custom JSONEncoder subclass for serializing Python ``stix2`` objects. - - If an optional property with a default value specified in the STIX 2 spec - is set to that default value, it will be left out of the serialized output. - - An example of this type of property include the ``revoked`` common property. - """ - - def default(self, obj): - if isinstance(obj, (dt.date, dt.datetime)): - return format_datetime(obj) - elif isinstance(obj, _STIXBase): - tmp_obj = dict(copy.deepcopy(obj)) - for prop_name in obj._defaulted_optional_properties: - del tmp_obj[prop_name] - return tmp_obj - else: - return super(STIXJSONEncoder, self).default(obj) - - -class STIXJSONIncludeOptionalDefaultsEncoder(json.JSONEncoder): - """Custom JSONEncoder subclass for serializing Python ``stix2`` objects. - - Differs from ``STIXJSONEncoder`` in that if an optional property with a default - value specified in the STIX 2 spec is set to that default value, it will be - included in the serialized output. - """ - - def default(self, obj): - if isinstance(obj, (dt.date, dt.datetime)): - return format_datetime(obj) - elif isinstance(obj, _STIXBase): - return dict(obj) - else: - return super(STIXJSONIncludeOptionalDefaultsEncoder, self).default(obj) - - -def serialize(obj, pretty=False, include_optional_defaults=False, **kwargs): - """ - Serialize a STIX object. - - Args: - obj: The STIX object to be serialized. - pretty (bool): If True, output properties following the STIX specs - formatting. This includes indentation. Refer to notes for more - details. (Default: ``False``) - include_optional_defaults (bool): Determines whether to include - optional properties set to the default value defined in the spec. - **kwargs: The arguments for a json.dumps() call. - - Returns: - str: The serialized JSON object. - - Note: - The argument ``pretty=True`` will output the STIX object following - spec order. Using this argument greatly impacts object serialization - performance. If your use case is centered across machine-to-machine - operation it is recommended to set ``pretty=False``. - - When ``pretty=True`` the following key-value pairs will be added or - overridden: indent=4, separators=(",", ": "), item_sort_key=sort_by. - """ - if pretty: - def sort_by(element): - return find_property_index(obj, *element) - - kwargs.update({'indent': 4, 'separators': (',', ': '), 'item_sort_key': sort_by}) - - if include_optional_defaults: - return json.dumps(obj, cls=STIXJSONIncludeOptionalDefaultsEncoder, **kwargs) - else: - return json.dumps(obj, cls=STIXJSONEncoder, **kwargs) - - def get_required_properties(properties): return (k for k, v in properties.items() if v.required) diff --git a/stix2/serialize.py b/stix2/serialize.py index e69de29..9a55598 100644 --- a/stix2/serialize.py +++ b/stix2/serialize.py @@ -0,0 +1,84 @@ +"""STIX2 core serialization methods.""" + +import copy +import datetime as dt + +import simplejson as json + +from .base import _STIXBase +from .utils import find_property_index, format_datetime + + +class STIXJSONEncoder(json.JSONEncoder): + """Custom JSONEncoder subclass for serializing Python ``stix2`` objects. + + If an optional property with a default value specified in the STIX 2 spec + is set to that default value, it will be left out of the serialized output. + + An example of this type of property include the ``revoked`` common property. + """ + + def default(self, obj): + if isinstance(obj, (dt.date, dt.datetime)): + return format_datetime(obj) + elif isinstance(obj, _STIXBase): + tmp_obj = dict(copy.deepcopy(obj)) + for prop_name in obj._defaulted_optional_properties: + del tmp_obj[prop_name] + return tmp_obj + else: + return super(STIXJSONEncoder, self).default(obj) + + +class STIXJSONIncludeOptionalDefaultsEncoder(json.JSONEncoder): + """Custom JSONEncoder subclass for serializing Python ``stix2`` objects. + + Differs from ``STIXJSONEncoder`` in that if an optional property with a default + value specified in the STIX 2 spec is set to that default value, it will be + included in the serialized output. + """ + + def default(self, obj): + if isinstance(obj, (dt.date, dt.datetime)): + return format_datetime(obj) + elif isinstance(obj, _STIXBase): + return dict(obj) + else: + return super(STIXJSONIncludeOptionalDefaultsEncoder, self).default(obj) + + +def serialize(obj, pretty=False, include_optional_defaults=False, **kwargs): + """ + Serialize a STIX object. + + Args: + obj: The STIX object to be serialized. + pretty (bool): If True, output properties following the STIX specs + formatting. This includes indentation. Refer to notes for more + details. (Default: ``False``) + include_optional_defaults (bool): Determines whether to include + optional properties set to the default value defined in the spec. + **kwargs: The arguments for a json.dumps() call. + + Returns: + str: The serialized JSON object. + + Note: + The argument ``pretty=True`` will output the STIX object following + spec order. Using this argument greatly impacts object serialization + performance. If your use case is centered across machine-to-machine + operation it is recommended to set ``pretty=False``. + + When ``pretty=True`` the following key-value pairs will be added or + overridden: indent=4, separators=(",", ": "), item_sort_key=sort_by. + """ + if pretty: + def sort_by(element): + return find_property_index(obj, *element) + + kwargs.update({'indent': 4, 'separators': (',', ': '), 'item_sort_key': sort_by}) + + if include_optional_defaults: + return json.dumps(obj, cls=STIXJSONIncludeOptionalDefaultsEncoder, **kwargs) + else: + return json.dumps(obj, cls=STIXJSONEncoder, **kwargs)