json-autotype

Automatic type declaration for JSON input data

https://github.com/mgajda/json-autotype

Version on this page:2.0.0@rev:1
LTS Haskell 14.27:3.0.1
Stackage Nightly 2019-09-21:3.0.1
Latest on Hackage:3.1.2

See all snapshots json-autotype appears in

BSD-3-Clause licensed by Michal J. Gajda
This version can be pinned in stack with:json-autotype-2.0.0@sha256:d5e2d93af3df9cdd9805025337c5bcab9b00dc354a9a724a34589bbe12668b03,13637

json-autotype

Takes a JSON format input, and generates automatic Haskell type declarations.

Parser and printer instances are derived using Aeson.

The program uses union type unification to trim output declarations. The types of same attribute tag and similar attribute set, are automatically unified using recognition by attribute set matching. (This option can be optionally turned off, or a set of unified types may be given explicitly.) :|: alternatives (similar to Either) are used to assure that all JSON inputs seen in example input file are handled correctly.

I should probably write a short paper to explain the methodology.

Build Status Hackage Hackage Dependencies

Details on official releases are on Hackage We currently support code generation to Haskell, and Elm.

Please volunteer help or financial support, if you want your favourite language supported too! Expression of interest may be filed as GitHub issue.

USAGE:

After installing with cabal install json-autotype, you might generate stub code for the parser:

    json-autotype input1.json ... inputN.json -o MyFormat.hs

Then you might test the parser by running it on an input file:

    runghc MyFormat.hs input.json

At this point you may see data structure generated automatically for you. The more input files you give to the inference engine json-autotype, the more precise type description will be.

Algorithm will also suggest which types look similar, based on a set of attribute names, and unify them unless specifically instructed otherwise.

The goal of this program is to make it easy for users of big JSON APIs to generate entries from example data.

Occasionally you might find a valid JSON for which json-autotype doesn’t generate a correct parser. You may either edit the resulting file and send it to the author as a test case for future release.

Patches and suggestions are welcome.

EXAMPLES:

The most simple example:

    {
        "colorsArray":[{
                "colorName":"red",
                "hexValue":"#f00"
            },
            {
                "colorName":"green",
                "hexValue":"#0f0"
            },
            {
                "colorName":"blue",
                "hexValue":"#00f"
            }
        ]
    }

It will produce the module with the following datatypes and TH calls for JSON parser derivations:

    data ColorsArray = ColorsArray {
        colorsArrayHexValue    :: Text,
        colorsArrayColorName :: Text
      } deriving (Show,Eq)

    data TopLevel = TopLevel {
        topLevelColorsArray :: ColorsArray
      } deriving (Show,Eq)

Note that attribute names match the names of JSON dictionary keys.

Another example with ambiguous types:

    {
        "parameter":[{
                "parameterName":"apiVersion",
                "parameterValue":1
            },
            {
                "parameterName":"failOnWarnings",
                "parameterValue":false
            },
            {
                "parameterName":"caller",
                "parameterValue":"site API"
            }]
    }

It will produce quite intuitive result (plus extra parentheses, and class derivations):

    data Parameter = Parameter {
        parameterParameterValue :: Bool :|: Int :|: Text,
        parameterParameterName :: Text
      }

    data TopLevel = TopLevel {
        topLevelParameter :: Parameter
      }

Real-world use case examples are provided in the package source repository.

Methodology:

  1. JSON-Autotype uses its own union type system to derive types from JSON documents as the first step.
  2. Then it finds all those records that have 90% of the same key names, and suggest them as similar enough to merit treating as instances of the same type. (Note that this is optional, and can be tuned manually.)
  3. Last step is to derive unique-ish type names - we currently do it by concatenating the name of the container and name of the key. (Please open PR, if you want something fancy about that - initial version used just key name, when it was unique.)
  4. Finally it generates Haskell or Elm code for the type.

Combination of robust union type system, and heuristic makes this system extremely reliable. Main test is QuickCheck-based generation of random JSON documents, and checking that they are all correctly parsed by resulting parser.

More details are described in Haskell.SG meetup presentation.

Other approaches:

  • There is a TypeScript type provider, and PLDI 2016 paper on solving this problem using preferred type shapes instead of union types. One can think about it as a alternative theory that gives very similar results, with more complicated exposition. It also does not tackle the problem of tagged records. It also does not attempt to guess unification candidates in order to reduce type complexity.

  • There was a json-sampler that allows to make simpler data structure from JSON examples, but doesn’t seem to perform unification, nor is it suitable for big APIs.

  • PADS project is another attempt to automatically infer types to treat arbitrary data formats (not just JSON). It mixes type declarations, with parsing/printing information in order to have a consistent view of both. It does not handle automatic type inference though.

  • JSON Schema generator uses .NET types to generate JSON Schema instead (in opposite direction.) Similar schema generation is used here

  • Microsoft Developer Network advocates use of Data Contracts instead to constrain possible input data.

Changes

Changelog

2.0.0  Jun 2018
    * Elm support completed with untagged unions
    * Add HaskellStrict option for running tests with -Werror, -Wall by default.
    * Make random tests run with -Werror, -Wall by default
    * Update dependencies to Aeson ranged between 1.2.1-1.4

1.1.2  Mar 2018
    * Fixed maintainer list.

1.1.1  Mar 2018
    * Fixed test builds (for Haskell).

1.1.0  Mar 2018
    * Partial support Elm code generation.

1.0.19  Nov 2017
    * Allow to have a custom name for toplevel data type.

1.0.18  Nov 2017
    * Fixed unit tests.
    * Fixed import for inclusion in Stackage.

1.0.17  Nov 2017
    * Fixed build and test issues.

1.0.16  Nov 2017
    * Dependencies updated to resolve #12, #15.
    * Fixed orphan Generic for Aeson >= 1.2.1 (#14).
    * Cleaned option parsing code.
    * Qualify GHC.Generics import.
    * Switch to optparse-applicative
    * Option to explicitly unify selected entries

1.0.15  Dec 2016
    * Support YAML input.

1.0.14  May 2016
    * Update to latest lens.

1.0.13  Mar 2016
    * Bumped up hint upper bound for v0.5.

1.0.12  Mar 2016

    * Fixed issue #8 - misrepresenting Double as Int.
    * Fixed issue #9 - efficient formatting with new Aeson-0.10 builder (toEncoding.)

1.0.11  Mar 2016

    * Updated to GHC 8.0

1.0.10  Sep 2015

    * Fixed bug appeared with aeson 0.10 breaking change:
https://github.com/bos/aeson/issues/287

1.0.8  Sep 2015

    * Dependency bump for lens 4.13 and aeson 0.10.

1.0.7  Jul 2015

    * Dependency bump for lens and vector.

1.0.6  Jun 2015

    * Make lens and aeson versions consistent in the *.cabal file.

1.0.3-1.0.5  Jun 2015

    * Bumped Aeson dependency up.
    * Tiny docs corrections.

1.0.2  Jun 2015

    * Relaxed dependency for lens-4.11.

1.0.1  Apr 2015

    * Relaxed dependency to lens-4.10.

1.0  Apr 2015

    * First stable release.

0.5  Apr 2015

    * Reduced name space pollution when generating code.
      Now all valid JSON test examples do work.
    * Corrected build failure on GHC 7.8.4

0.4  Apr 2015

    * Release candidate for current functionality.

0.3  Apr 2015

    * Passed all smallcheck/quickcheck tests.
    * Approaching release candidate.

0.2.5.13  Apr 2015

    * Correctly handling lone option, not yet union with optionality.
      Fixed: #3.

0.2.5.12  Apr 2015

    * Added typechecking before and after type unification.
    * Added shrink for more informative QuickCheck testing.
    * Tested mostly using GHC 7.10.

0.2.5.11  Mar 2015

    * Add short versions of command line flags: -o, -d, and -t.

0.2.5.10  Mar 2015

    * Bump up lens dependency.

0.2.5.8  Mar 2015

    * Updated tests and build config.

0.2.5.7  Mar 2015

    * Fixed documentation anchors, and unit test classification for failures.

0.2.5.6  Mar 2015

    * Relaxed upper bounds for lens 4.8.

0.2.5.5  Mar 2015

    * (Skipped this version number by mistake.)

0.2.5.4  Dec 2014

    * Relaxed upper bounds for new lens.

0.2.5.3  Dec 2014

    * Relaxed upper bounds again.

0.2.5.2  Dec 2014

    * Updated metainfo, relaxed upper bounds for GHC 7.10.

0.2.5.0  Nov 2014

    * Nicer union type syntax in Data.Aeson.AutoType.Alternative.

0.2.4.0  Nov 2014

    * To assure proper treatment of unions,
      I make them with Data.Aeson.AutoType.Alternative type instead of Either.

0.2.3.0  Nov 2014

    * Explicit JSON parser generation to avoid conflicts between Haskell keywords and field names.
    * Renaming of Haskell field names with a prefix of object name (data type.)

0.2.2.0  Nov 2014

    * GenerateJSONParser may now take multiple input samples to produce single parser.
    * Fixed automated testing for all example files.

0.2.1.4  Oct 2014

    * Added examples to the package distribution.

0.2.1.3  Oct 2014

    * Cleaned up package.
    * Changelog in markdown format.

0.2.1  Oct 2014

    * Added option to use it as a filter ('-' is accepted input name.)

0.2.0  Oct 2014

    * First release to Hackage.
    * Handling of proper unions, and most examples.
    * Automatically tested on a wide range of example documents (see
    tests/)
    * Initial documentation in README.md.

0.1.0  July 2014

* First experiments uploaded to GitHub, and discussed to
HackerSpace.SG.