Git Product home page Git Product logo

haven-json's Introduction

Build Status Issue Count Contributions Welcome Documentation: Read Now

Haven

Haven is a simple JSON library written in pure Kotlin with no third-party dependencies. It was designed with simplicity of use in mind, with the goal of letting the user manipulate JSON with an almost Javascript-like syntax.

Quick Links:

The Haven Philosophy

  • Simple is better than complex
  • Concise and intuitive is better than verbose and idiomatic
  • Dependencies are a liability
  • Correctness is not the gold standard, it's the baseline

In order to achieve this, Haven:

  • Makes accessing JSON on the fly as simple as possible, providing type safety without forcing the user to think about the static types of their data at every step
  • Provides a DSL syntax as Javascript-like as possible within the restraints of the Kotlin language
  • Uses no third-party dependencies other than the kotlintest testing framework
  • Provides extensive test cases to ensure there are as few bugs and inconsistencies as possible

What makes Haven different?

Haven primarily takes a tree-model approach to dealing with JSON data - parsed JSON data is an in-memory tree of objects that can be deeply accessed on demand. Most JSON libraries available for the JVM take a data-binding approach - you define an object that serves as the structure, which the library then maps the JSON values onto. This difference in approach makes Haven much quicker and simpler to get down and dirty with your data, just like if it was stored in native Map and List objects (because it is!), but it has it's drawbacks as well, namely in type-safety and a hard hit to syntax conciseness. Haven aims to minimize these issues as much as possible, giving you a simple, concise, easy to understand way to interface with your data, while still being type-safe (and without all the type-safety syntax clutter).

Haven also supports a data-binding style of JSON manipulation, similar to most existing JVM libraries; however, this functionality is built on top of the core tree-model system and is really somewhat of a second class citizen. It's recommended to use the tree-model style for simple interactions with your data, and apply the data binding approach primarily when your code needs to interface with other outside code (for example, returning an object instead of raw JSON from an API call convenience method).

Installation

You can install Haven with JitPack (Gradle example shown below), check out the Releases page, or download the source code and build it yourself.

allprojects {
  repositories {
    // ...
    maven { url 'https://jitpack.io' }
  }
}

dependencies {
  implementation 'com.github.swordglowsblue:haven-json:1.1.2'
}

Development To-Do List

  • Basic JSON AST structure
  • DSL for creating JSON objects
  • DSL for accessing JSON objects
  • DSL for modifying JSON objects
  • Methods to convert JSON objects to valid JSON strings
    • Minified
    • Pretty-printed at any indentation size
  • JSON.org spec compliant JSON parser
  • Data-binding style destructuring

Examples

Importing

import com.tripl3dogdare.havenjson.*

Creating JSON via the DSL

val jwick:Json = Json(
  "name" to "John Wick",
  "age" to 37,
  "occupation" to "Hitman",
  "contacts" to listOf(
    mapOf("name" to "Ethan Hunt", "number" to "[REDACTED]"),
    mapOf("name" to "Jason Bourne", "number" to "[REDACTED]"),
    mapOf("name" to "Bender", "number" to "2716057")
  )
)

Accessing JSON via the DSL

// Untyped value access
// Returns null if the value doesn't exist
val jwickName:Any? = jwick["name"].value

// Typed value access
// Returns null if the value doesn't exist or is the wrong type
val jwickName = jwick["name"].value<String>()
val jwickAge= jwick["age"].asInt
val ehuntNumber = jwick["contacts"][0]["number"].asString

Parsing JSON from a string

val string = """
  {
    "name": "John Wick",
    "age": 37,
    "occupation": "Hitman",
    "contacts": [
      { "name": "Ethan Hunt", "number": "[REDACTED]" },
      { "name": "Jason Bourne", "number": "[REDACTED]" },
      { "name": "Bender", "number": "2716057" }
    ]
  }
"""

val jwick:Json = Json.parse(string)

Converting JSON back to a string

val jwickString2 = jwick.mkString())  // Default is 2-space indentation
val jwickString0 = jwick.mkString(0)) // Minified
val jwickString4 = jwick.mkString(4)) // Custom indentation depth in spaces

Deserializing a class instance from JSON

class Field(name:String, value:String) : JsonSchema

// The first parameter can either be a constructor function (::Name) or a class instance (Name::class)
//   The passed function or given class's primary constructor must return a subtype of JsonSchema!
// The second parameter can either be a string (will be parsed as JSON) or an existing Json instance
val field = Json.deserialize(::Field, """{"name":"Test","value":"test"}""")

Deserializing with a name converter function

class Thing(thingName:String) : JsonSchema

// The optional third parameter can be any (String) -> String function
// JsonSchema provides some common defaults like snake case to camel case (shown here)
val field = Json.deserialize(::Thing, """{"thing_name":"Bob"}""", JsonSchema.SNAKE_TO_CAMEL)

Deserializing with overridden property names

class Thing( @JsonProperty("thing_name") thingName:String ) : JsonSchema

// This will do the same as the above; useful for when a particular field doesn't follow conventions
//   or you'd like to explicitly rename something
val field = Json.deserialize(::Thing, """{"thing_name":"Bob"}""")

Custom deserializer functions

class WithDate(date:ZonedDateTime) : JsonSchema

// Register a deserializer function with the default group
// The input and output types of the given function are used to determine what to use
// Only one deserializer function is allowed per output type
Deserializers.add(ZonedDateTime::parse) // (String) -> ZonedDateTime
val withDate = Json.deserialize(::WithDate, """{"date":"2018-07-05T18:13:59+00:00"}""")

// Custom deserializer group
// Useful for having different ways to deserialize a particular output type or not leaking deserializers
//   to other functions
val deser = Deserializers().add(ZonedDateTime::parse)
val withDate = Json.deserialize(::WithDate, """{"date":"2018-07-05T18:13:59+00:00"}""", deserializers = deser)

haven-json's People

Contributors

kgscialdone avatar rmadlal avatar tripl3dogdare avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

haven-json's Issues

Add support for modifying JSON objects

Currently, there is no simple way to edit a JSON object once it's been created. The current syntax for accessing a JSON property is:

json["obj_name"]["prop_name"]

The intuitive syntax for editing a JSON object would be:

json["obj_name"]["prop_name"] = newValue

Unfortunately, I'm not entirely sure how to implement this. I don't want to just make the internal List<Json> and Map<String, Json> objects mutable, and I can't see a good way to do it otherwise without some serious tree-walking. I'd also hope to be able to get other convenience methods like map, filter, and so on. Needs research.

Add automatic serializer functions to JsonSchema classes

Right now, it's fairly easy to deserialize JSON into a class inheriting from JsonSchema. However, there's no efficient or built-in way to reverse this and create JSON from a JsonSchema. I'd like to add this by the 1.2.0 release.

Add source position to parse error message

Currently, Tokens don't track their location in the JSON source they came from. By changing this, it would allow for better error messages, changing errors like:

JsonParseError: Unexpected } when parsing JSON

into errors like:

JsonParseError: Unexpected } when parsing JSON (line 10, col 7)

This would make the library much easier to debug with, allowing users to see at a glance where a particular parse error came from.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.