Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
This is the initial standalone version extracted from the
`django-local-settings` project.
  • Loading branch information
wylee committed Nov 4, 2021
1 parent 8e3bb49 commit 20cfd9a
Show file tree
Hide file tree
Showing 54 changed files with 2,819 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Change log for jsun

## 1.0 - unreleased

This is the initial standalone version of this package. It was spun out
of the [https://github.com/wylee/django-local-settings](django-local-settings)
package.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2014, 2021 Wyatt Baldwin <self@wyattbaldwin.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# jsun

This is an alternative JSON decoder/encoder that supports some extra
features. It takes a *lot* of inspiration from the `json` module in the
standard library and exposes the same high level API: `load`, `loads`,
`dump`, `dumps`, `JSONDecoder`, and `JSONEncoder`.

In many cases, `jsun` can be swapped in by installing the package then
simply updating imports to use `jsun` instead of `json`.

## Extra decoding features

- Trailing commas

- Line comments starting with //

- All valid Python ints and floats:
- Binary, octal, hex
- Underscore separators
- Unary plus operator

- Math constants:
- inf, nan, E, π, PI, τ, TAU
- Infinity, NaN

- Literal (unquoted) dates and times:
- 2021-06
- 2021-06-23
- 2021-06-23T12:00
- 2021-06-23T12:00Z
- 2021-06-23T12:00-07:00
- 12:00 (today's date at noon)

- Decoding an empty string will produce `None` rather than an exception
(an exception will be raised if extras are disabled)

- *All* parsing methods can be overridden if some additional
customization is required. In particular, the object and array
parsers can be overridden

- A pre-parse method can be provided to handle values before the regular
JSON parsers are applied

- A fallback parsing method can be provided to handle additional types
of values if none of the default parsers are suitable

- When errors are encountered, specific exceptions are raised (all
derived from the built-in `ValueError`)

## Extra encoding features

The `jsun` encoder is very similar to the standard library encoder (and
is in fact a subclass of `json.JSONEncoder`). Currently, it supports
only a couple of extra features:

- Date objects are converted to ISO format by default
- Datetime objects are converted to ISO format by default

NOTE: There is some asymmetry here. E.g., date and datetime objects
should be converted to literals instead of quoted strings.

## Disabling the extra features

*All* the extra features can be turned off with a flag:

>>> from jsun import decode
>>> decode("[1, 2, 3,]")
[1, 2, 3]
>>> decode("[1, 2, 3,]", enable_extras=False)
<exception traceback>

## Differences between jsun and standard library json

- An empty string input is converted to `None` rather than raising an
exception (only if extras are enabled).

- When decoding, instead of `object_hook` and `object_hook_pairs`,
there's just a single `object_converter` argument. It's essentially
the same as `object_hook`. `object_hook_pairs` seems unnecessary
nowadays since `dict`s are ordered.

- The default object type is `jsun.obj.JSONObject` instead of `dict`. A
`JSONObject` is a bucket of properties that can be accessed via dotted
or bracket notation. Pass `object_converter=None` to get back
`dict`s instead.

## Config files

A bonus feature is that configuration can be loaded from INI files
where the keys are split on dots to create sub-objects and the values
are encoded as JSON.

This is quite similar to TOML and some of the features of `jsun`, like
literal dates, are inspired by TOML.

This feature was originally developed in 2014 as part of the
`django-local-settings` project, about a year and half after TOML was
first released but before I'd heard of it.

### Differences with TOML

- Parentheses are used instead of quotes to avoid splitting on dots
- Objects created using `{}` syntax (AKA "inline tables" in TOML) can
span multiple lines
- There are no arrays of tables
- Others I'm not thinking of at the moment...

## About the name

My first choice was `jsonish` but that's already taken. My second choice
was `jsonesque` but it's also taken, and it's hard to type. `jsun` is
nice because it's easy to type and easy to swap in for `json` by just
changing a single letter.

## Testing

There's a suite of unit tests, which also tests against the JSON checker
files at https://json.org/JSON_checker/. Coverage is currently at 82%.
35 changes: 35 additions & 0 deletions commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from runcommands import command
from runcommands.commands import local as _local


@command
def format_code(check=False):
_local(f"black . {'--check' if check else ''}")


@command
def lint():
_local("flake8 .")


@command
def test(with_coverage=True, check=True, fail_fast=False):
if with_coverage:
_local(
"coverage run "
"--source src/jsun "
"-m unittest discover "
"-t . -s tests "
"&& coverage report"
)
else:
fail_fast = "-f" if fail_fast else ""
_local(f"python -m unittest discover -t . -s tests {fail_fast}")
if check:
format_code(check=True)
lint()


@command
def tox(clean=False):
_local(f"tox {'-r' if clean else ''}")
Loading

0 comments on commit 20cfd9a

Please sign in to comment.