# JSON utilities

JSON (de)serialization framework.

The framework presented here is somewhat based on Go’s “json” package (especially the `omitempty` functionality).

josepy.json_util.field(json_name: str, default: Any = None, omitempty: bool = False, decoder: Callable[[Any], Any] | None = None, encoder: Callable[[Any], Any] | None = None) → Any

  Convenient function to declare a `Field` with proper type annotations.

  This function allows to write the following code:

  import josepy class JSON(josepy.JSONObjectWithFields):

  > typ: str = josepy.field(‘type’)

  > def other_type(self) -> str:

  >   return self.typ

class josepy.json_util.Field(json_name: str, default: Any = None, omitempty: bool = False, decoder: Callable[[Any], Any] | None = None, encoder: Callable[[Any], Any] | None = None)

  JSON object field.

  `Field` is meant to be used together with `JSONObjectWithFields`.

  `encoder` (`decoder`) is a callable that accepts a single parameter, i.e. a value to be encoded (decoded), and returns the serialized (deserialized) value. In case of errors it should raise `SerializationError` (`DeserializationError`).

  Note, that `decoder` should perform partial serialization only.

  Variables:

    - json_name (str) – Name of the field when encoded to JSON.

    - default – Default value (used when not present in JSON object).

    - omitempty (bool) – If `True` and the field value is empty, then it will not be included in the serialized JSON object, and `default` will be used for deserialization. Otherwise, if `False`, field is considered as required, value will always be included in the serialized JSON objected, and it must also be present when deserializing.

  omit(value: Any) → bool

    Omit the value in output?

  decoder(fdec: Callable[[Any], Any]) → Field

    Descriptor to change the decoder on JSON object field.

  encoder(fenc: Callable[[Any], Any]) → Field

    Descriptor to change the encoder on JSON object field.

  decode(value: Any) → Any

    Decode a value, optionally with context JSON object.

  encode(value: Any) → Any

    Encode a value, optionally with context JSON object.

  classmethod default_decoder(value: Any) → Any

    Default decoder.

    Recursively deserialize into immutable types ( `josepy.util.frozendict` instead of `dict()`, `tuple()` instead of `list()`).

  classmethod default_encoder(value: Any) → Any

    Default (passthrough) encoder.

class josepy.json_util.JSONObjectWithFieldsMeta(name: str, bases: List[str], namespace: Dict[str, Any])

  Metaclass for `JSONObjectWithFields` and its subclasses.

  It makes sure that, for any class `cls` with `__metaclass__` set to `JSONObjectWithFieldsMeta`:

  1. All fields (attributes of type `Field`) in the class definition are moved to the `cls._fields` dictionary, where keys are field attribute names and values are fields themselves.

  2. `cls.__slots__` is extended by all field attribute names (i.e. not `Field.json_name`). Original `cls.__slots__` are stored in `cls._orig_slots`.

  In a consequence, for a field attribute name `some_field`, `cls.some_field` will be a slot descriptor and not an instance of `Field`. For example:

      some_field = Field('someField', default=())

      class Foo:
          __metaclass__ = JSONObjectWithFieldsMeta
          __slots__ = ('baz',)
          some_field = some_field

      assert Foo.__slots__ == ('some_field', 'baz')
      assert Foo._orig_slots == ()
      assert Foo.some_field is not Field

      assert Foo._fields.keys() == ['some_field']
      assert Foo._fields['some_field'] is some_field

  As an implementation note, this metaclass inherits from `abc.ABCMeta` (and not the usual `type`) to mitigate the metaclass conflict (`ImmutableMap` and `JSONDeSerializable`, parents of `JSONObjectWithFields`, use `abc.ABCMeta` as its metaclass).

class josepy.json_util.JSONObjectWithFields(**kwargs: Any)

  JSON object with fields.

  Example:

      class Foo(JSONObjectWithFields):
          bar = Field('Bar')
          empty = Field('Empty', omitempty=True)

          @bar.encoder
          def bar(value):
              return value + 'bar'

          @bar.decoder
          def bar(value):
              if not value.endswith('bar'):
                  raise errors.DeserializationError('No bar suffix!')
              return value[:-3]

      assert Foo(bar='baz').to_partial_json() == {'Bar': 'bazbar'}
      assert Foo.from_json({'Bar': 'bazbar'}) == Foo(bar='baz')
      assert (Foo.from_json({'Bar': 'bazbar', 'Empty': '!'})
              == Foo(bar='baz', empty='!'))
      assert Foo(bar='baz').bar == 'baz'

  encode(name: str) → Any

    Encode a single field.

    Parameters:

      name (str) – Name of the field to be encoded.

    Raises:

      - errors.SerializationError – if field cannot be serialized

      - errors.Error – if field could not be found

  fields_to_partial_json() → Dict[str, Any]

    Serialize fields to JSON.

  to_partial_json() → Dict[str, Any]

    Partially serialize.

    Following the example, partial serialization means the following:

        assert isinstance(Bar().to_partial_json()[0], Foo)
        assert isinstance(Bar().to_partial_json()[1], Foo)

        # in particular...
        assert Bar().to_partial_json() != ['foo', 'foo']

    Raises:

      josepy.errors.SerializationError – in case of any serialization error.

    Returns:

      Partially serializable object.

  classmethod fields_from_json(jobj: Mapping[str, Any]) → Any

    Deserialize fields from JSON.

  classmethod from_json(jobj: Mapping[str, Any]) → GenericJSONObjectWithFields

    Deserialize a decoded JSON document.

    Parameters:

      jobj – Python object, composed of only other basic data types, as decoded from JSON document. Not necessarily `dict` (as decoded from “JSON object” document).

    Raises:

      josepy.errors.DeserializationError – if decoding was unsuccessful, e.g. in case of unparseable X509 certificate, or wrong padding in JOSE base64 encoded string, etc.

josepy.json_util.encode_b64jose(data: bytes) → str

  Encode JOSE Base-64 field.

  Parameters:

    data (bytes)

  Return type:

    `str`

josepy.json_util.decode_b64jose(data: str, size: int | None = None, minimum: bool = False) → bytes

  Decode JOSE Base-64 field.

  Parameters:

    - data (unicode)

    - size (int) – Required length (after decoding).

    - minimum (bool) – If `True`, then `size` will be treated as minimum required length, as opposed to exact equality.

  Return type:

    bytes

josepy.json_util.encode_hex16(value: bytes) → str

  Hexlify.

  Parameters:

    value (bytes)

  Return type:

    unicode

josepy.json_util.decode_hex16(value: str, size: int | None = None, minimum: bool = False) → bytes

  Decode hexlified field.

  Parameters:

    - value (unicode)

    - size (int) – Required length (after decoding).

    - minimum (bool) – If `True`, then `size` will be treated as minimum required length, as opposed to exact equality.

  Return type:

    bytes

josepy.json_util.encode_cert(cert: ComparableX509) → str

  Encode certificate as JOSE Base-64 DER.

  Return type:

    unicode

josepy.json_util.decode_cert(b64der: str) → ComparableX509

  Decode JOSE Base-64 DER-encoded certificate.

  Parameters:

    b64der (unicode)

  Return type:

    `OpenSSL.crypto.X509` wrapped in `ComparableX509`

josepy.json_util.encode_csr(csr: ComparableX509) → str

  Encode CSR as JOSE Base-64 DER.

  Return type:

    unicode

josepy.json_util.decode_csr(b64der: str) → ComparableX509

  Decode JOSE Base-64 DER-encoded CSR.

  Parameters:

    b64der (unicode)

  Return type:

    `OpenSSL.crypto.X509Req` wrapped in `ComparableX509`

class josepy.json_util.TypedJSONObjectWithFields(**kwargs: Any)

  JSON object with type.

  typ: str = NotImplemented

    Type of the object. Subclasses must override.

  type_field_name: str = 'type'

    Field name used to distinguish different object types.

    Subclasses will probably have to override this.

  TYPES: Dict[str, Type] = NotImplemented

    Types registered for JSON deserialization

  classmethod register(type_cls: Type[GenericTypedJSONObjectWithFields], typ: str | None = None) → Type[GenericTypedJSONObjectWithFields]

    Register class for JSON deserialization.

  classmethod get_type_cls(jobj: Mapping[str, Any]) → Type[TypedJSONObjectWithFields]

    Get the registered class for `jobj`.

  to_partial_json() → Dict[str, Any]

    Get JSON serializable object.

    Returns:

      Serializable JSON object representing ACME typed object. `validate()` will almost certainly not work, due to reasons explained in `josepy.interfaces.IJSONSerializable`.

    Return type:

      dict

  classmethod from_json(jobj: Mapping[str, Any]) → TypedJSONObjectWithFields

    Deserialize ACME object from valid JSON object.

    Raises:

      josepy.errors.UnrecognizedTypeError – if type of the ACME object has not been registered.