All Projects → aishfenton → Argus

aishfenton / Argus

Licence: MIT License
Builds models from JSON Schemas

Programming Languages

scala
5932 projects

Projects that are alternatives of or similar to Argus

active model serializers validator
🃏 An extension to ActiveModel::Serializer that validates serializers output against a JSON schema
Stars: ✭ 18 (-83.02%)
Mutual labels:  json-schema
form-pa
A flexible and configurable form based on json schema
Stars: ✭ 13 (-87.74%)
Mutual labels:  json-schema
svelte-form
JSON Schema form for Svelte v3
Stars: ✭ 47 (-55.66%)
Mutual labels:  json-schema
json2object
Type safe Haxe/JSON (de)serializer
Stars: ✭ 54 (-49.06%)
Mutual labels:  json-schema
JSON-NLP
JSON-NLP Schema for transfer of NLP output using JSON
Stars: ✭ 38 (-64.15%)
Mutual labels:  json-schema
jsf
Creates fake JSON files from a JSON schema
Stars: ✭ 46 (-56.6%)
Mutual labels:  json-schema
fhir-fuel.github.io
Place to prepare proposal to FHIR about JSON, JSON-Schema, Swagger/OpenAPI, JSON native databases and other JSON-frendly formats (yaml, edn, avro, protobuf etc) and technologies
Stars: ✭ 20 (-81.13%)
Mutual labels:  json-schema
openapi-schemas
JSON Schemas for every version of the OpenAPI Specification
Stars: ✭ 22 (-79.25%)
Mutual labels:  json-schema
home
This is the home page for the API specification toolbox.
Stars: ✭ 16 (-84.91%)
Mutual labels:  json-schema
json-schema-sugar
Create a JSON Schema without the pain of writing it 🍦
Stars: ✭ 20 (-81.13%)
Mutual labels:  json-schema
opis-error-presenter
JSON-schema error presenter for opis/json-schema library
Stars: ✭ 17 (-83.96%)
Mutual labels:  json-schema
schema-shot
Framework-agnostic snapshot testing using "schema by example" for highly dynamic data
Stars: ✭ 34 (-67.92%)
Mutual labels:  json-schema
i18n-tag-schema
Generates a json schema for all i18n tagged template literals in your project
Stars: ✭ 15 (-85.85%)
Mutual labels:  json-schema
cti-stix2-json-schemas
OASIS TC Open Repository: Non-normative schemas and examples for STIX 2
Stars: ✭ 75 (-29.25%)
Mutual labels:  json-schema
json-schema-inferrer
Java library for inferring JSON schema from sample JSONs
Stars: ✭ 78 (-26.42%)
Mutual labels:  json-schema
schemaorg-jsd
JSON Schema validation for JSON-LD files using Schema.org vocabulary.
Stars: ✭ 16 (-84.91%)
Mutual labels:  json-schema
commun
🎩 Fully-featured framework for REST APIs and GraphQL from JSON Schema with TypeScript and MongoDB
Stars: ✭ 97 (-8.49%)
Mutual labels:  json-schema
caddy-json-schema
JSON schema generator for Caddy v2
Stars: ✭ 63 (-40.57%)
Mutual labels:  json-schema
grunt-tv4
Use Grunt and Tiny Validator tv4 to validate files against json-schema draft v4
Stars: ✭ 13 (-87.74%)
Mutual labels:  json-schema
kloadgen
KLoadGen is kafka load generator plugin for jmeter designed to work with AVRO and JSON schema Registries.
Stars: ✭ 144 (+35.85%)
Mutual labels:  json-schema

Argus (船大工)

TravisCI

Scala macros for generating code from Json Schemas. Any structures defined within the schema (such as properties, enums, etc) are used to generate scala code at compile time. Json encoder/decoders are also generated for the Circe Json library.

NB: Why Argus? In keeping with the theme of Argonaut and Circe, Argus (son of Arestor) was the builder of the ship "Argo", on which the Argonauts sailed.

Quick Example

Starting with this Json schema.

{
  "title": "Example Schema",
  "type": "object",
  "definitions" : {
    "Address": {
      "type": "object",
      "properties": {
        "number" : { "type": "integer" },
        "street" : { "type": "string" }
      }
    },
    "ErdosNumber": {
      "type": "integer"
    }
  },
  "properties": {
    "name": {
      "type": "array",
      "items": { "type": "string" }
    },
    "age": {
      "description": "Age in years",
      "type": "integer"
    },
    "address" : { "$ref" : "#/definitions/Address" },
    "erdosNumber" : { "$ref" : "#/definitions/ErdosNumber" }
  }
}

We can use the @fromSchemaResource macro to generate case classes for (Root, Address)

import argus.macros._
import io.circe._
import io.circe.syntax._

@fromSchemaResource("/simple.json", name="Person")
object Schema
import Schema._
import Schema.Implicits._

val json = """
   |{
   |  "name" : ["Bob", "Smith"],
   |  "age" : 26,
   |  "address" : {
   |    "number" : 31,
   |    "street" : "Main St"
   |  },
   |  "erdosNumber": 123
   |}
  """.stripMargin

// Decode into generated case class
val person = parser.decode[Person](json).toOption.get

// Update address 
val address = Address(number=Some(42), street=Some("Alt St"))
val newPerson = person.copy(address=Some(address))

// Encode base to json
newPerson.asJson 

Many more examples here

Rules

Supported constructs

Object templates (i.e. classes)

JsonGenerated Scala
{
  "properties" : { 
    "name" : { "type" : "string" },
    "age"  : { "type" : "integer" }
  },
  "required" : ["name"]
}
case class Root(name: String, age: Option[Int] = None)

Basic types

json typejson formatGenerated Scala type
string
*
String
string
uuid
java.util.UUID
string
date-time
java.time.ZonedDateTime
integer
* | int32
Int
integer
int64
Long
integer
int16
Short
integer
int8
Byte
number
* | double
Double
number
single
Float
boolean
*
Boolean
null
*
Null

Definitions (i.e. common class definitions)

JsonGenerated Scala
{
  "definitions" : { 
    "Person" : { ... },
    ...
  }
  "properties" : { 
    "person" : { "$ref" : "#/definitions/Person" }
  }
}
case class Person(...)
case class Root(person: Option[Person] = None)

OneOf (i.e. type A or B)

JsonGenerated Scala
{
  "oneOf": [
    { "$ref" : "#/definitions/Address" },
    { "type" : "number" }
  ]
}
@union sealed trait RootUnion
case class RootAddress(...) extends RootUnion
case class RootDouble(...) extends RootUnion

Enums

JsonGenerated Scala
{
  "properties": { 
    "countries" : { "enum" : ["NZ", "US", "UK"] }
  }
}
@enum sealed trait Countries
object CountriesEnum {
  case object NZ(...) extends Countries
  case object US(...) extends Countries
  ...
}
case class Root(countries: Option[Countries] = None)

Arrays

JsonGenerated Scala
{
  "properties": { 
    "places" : { "items" : { "type": "string" } }
  }
}
case class Root(places: Option[List[String]] = None)

Any Types (i.e. when a field can take arbitrary values)

JsonGenerated Scala
{
  "properties": { 
    "values" : { }
  }
}
case class Values(x: Any)
case class Root(values: Option[Values] = None)

Unsupported

  • Only Circe encoders/decoders are supported, although the skeleton is laid out for adding support for other Json libraries.
  • anyOf / allOf. Should be simple to add, just haven't done it yet.
  • default. Schemas can specify the default value to use for a field. Currently we just ignore these.
  • not. Not sure how you could specify this in a type language. Needs more thoughts
  • additionalProperties, and additionalItems. Json schema lets you specify free-form properties too. These are unsupported for now (although I think this should be easy now there's supprot for Any types)
  • patternProperties. What should be the objects fields in this case? Maybe we just make it a Map?
  • dependencies. Dependencies can also extend the schema... sigh.
  • Any of the validation-only info, such as maxItems, since it doesn't contribute to the structure.

And lastly: We only generate code from json schemas, but we can't generate json-schema from code. This is fully possible, but requires work ;)

There's still a lot to do! Looking for contributors to address any of above.

Usage Tips

  1. All macros support arguments debug=true and outPath="...". debug causes the generated code to be dumped to stdout, and outPath causes the generated code to be written to a file. The optional argument outPathPackage allows to specify a package name for the output file.

    @fromSchemaResource("/simple.json", debug=true, outPath="/tmp/Simple.Scala", outPathPackage="argus.simple")
    object Test
  2. You can generate code from inline json schemas. Also supported are fromSchemaInputStream and fromSchemaURL too.

    @fromSchemaJson("""
    {
      "properties" : { 
        "name" : { "type" : "string" },
        "age"  : { "type" : "integer" }
      },
      "required" : ["name"]
    }
    """)
    object Schema
  3. You can name the root class that is generated via the name="..." argument.

    @fromSchemaResource("/simple.json", name="Person")
    object Schema
    import Schema.Person
  4. Within the object we also generate json encoder/decoder implicit variables, but you need to import them into scope.

    @fromSchemaResource("/simple.json", name="Person")
    object Schema
    import Schema._
    import Schema.Implicits._
    
    Person(...).asJson
  5. You can override specific Encoders/Decoders. All implicits are baked into a trait called LowPriorityImplicits. Rather than importing Foo.Implicits you can make your own implicits object that extends this and provides overrides. For example:

    @fromSchemaResource("/simple.json")
    object Foo
    import Foo._
    
    object BetterImplicits extends Foo.LowPriorityImplicits {
      implicit val myEncoder: Encoder[Foo.Root] =   ... 
      implicit val betterDecoder: Decoder[Foo.Root] = ...
    }
    import BetterImplicits._
  6. Free form Json (we call them Any types above) are quite common within Json schemas. These are fields that are left open to take any kind of Json chunk (maybe for additional properties, or data, etc). Unfortunately they presents a challenge in a strongly typed language, such as Scala, where we always need some kind of type.

    The approach we've taken is to wrap these chunks in their own case class which has a single field of type Any. This also allows you to override the encode/decoders for that type (Root.Data in this example) with something more custom if required.

    @fromSchemaJson("""
    {
      "type": "object",
      "properties" : { 
        "data" : { "type": "array", "items": { } }
      }
    }
    """)
    object Schema
    import Schema._
    import Schema.Implicits._
    
    val values = List( Root.Data(Map("size" -> 350, "country" -> "US")), Root.Data(Map("size" -> 350, "country" -> "US")) )
    Root(data=Some(values))

    The default encoder/decoder (as shown in the code example above) works if your types are:

    • Primitive types: Boolean, Byte, Short, Int, Long, Float, Double
    • Primate arrays (Array[Byte], Array[Int], etc)
    • Seq[Any] (and subclasses) where Any needs to be one of the types in this list
    • Maps[String, Any], where Any needs to be one of the types in this list.

    Or, in other words, you can't stick arbitrary objects in the Any wrapper and expect their encoders/decoders to get picked up. If you need that then you'll have to override the default encoder/decoder for this type.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].