moznion's tech blog

moznion's technical memo

Released go-json-ice: a code generator of JSON marshaler for tinygo

In tinygo, if the go code imports encoding/json package, it fails to compile. AFAIK, this is a known issue *1, so it seemed there is no standard way to marshal any struct to JSON string. There are some related issues like the following:

github.com

github.com

The point is if we would like to marshal a struct to JSON string, "serialize it by hand, carefully" is an only way for that, but it's a little too inconvenient so I made a code generator of JSON marshaler for that purpose; the generated marshaler doesn't have the dependency belongs to encoding/json.

github.com

This library parses the struct (i.e. its json custom struct tag) that to marshal beforehand and generates marshaler code according to the parsed result. There have been already similar implementations, such as mailru/easyjson, but they depend on encoding/json internally so it hasn't matched with my purpose.

For example, if you'd like to marshal the following struct, just put go:generate with that,

//go:generate json-ice --type=AwesomeStruct
type AwesomeStruct struct {
    Foo string `json:"foo"`
    Bar string `json:"bar,omitempty"`
}

then, it generates marshaler code as MarshalAwesomeStructAsJSON(s *AwesomeStruct) ([]byte, error), next you can marshal a struct to JSON with that:

marshaled, err := MarshalAwesomeStructAsJSON(&AwesomeStruct{
    Foo: "buz",
    Bar: "",
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%s\n", marshaled) // => {"foo":"buz"}

Therefore, it no longer needed to use reflection for marshaling, so tinygo can marshal struct easily. And also it resolves fields of marshaling target struct in advance, thus the performance gets better. Of course, it has side effects. If the target field type was interface{}, it cannot marshal that because it can't resolve the actual type of the field dynamically. The type must be resolvable as static.

And as you know, tinygo can transpile to wasm, and the modules that are imported in wasm on running depends on what original code depends on. The generated code from this library is interested to keep that dependency as minimal as possible because there is the possibility to execute wasm in a super-restricted sandbox *2. As a result, the code only relies on strconv package.

I'm happy if you try this library!

FYI, this library only supports JSON marshaling. If you're looking for a way to unmarshal JSON in tinygo, I guess buger/jsonparser would help you for the purpose:

It does not rely on encoding/json, reflection or interface{}, the only real package dependency is bytes.

github.com


By the way,

//go:generate json-ice --type=DeepStruct
type DeepStruct struct {
    Deep []map[string]map[string]map[string]map[string]string `json:"deep"`
}

this library can generate the marshaler code for a struct that has deep and recursive field type like the above.

given := &DeepStruct{
    Deep: []map[string]map[string]map[string]map[string]string{
        {
            "foo": {
                "bar": {
                    "buz": {
                        "qux": "foobar",
                    },
                },
            },
        },
        {
            "foofoo": {
                "barbar": {
                    "buzbuz": {
                        "quxqux": "foobarfoobar",
                    },
                },
            },
        },
    },
}

marshaled, err := MarshalDeepStructAsJSON(given)
if err != nil {
    log.Fatal(err)
}

log.Printf("[debug] %s", marshaled) // => {"deep":[{"foo":{"bar":{"buz":{"qux":"foobar"}}}},{"foofoo":{"barbar":{"buzbuz":{"quxqux":"foobarfoobar"}}}}]}

The generated code for that is the following :)

import "github.com/moznion/go-json-ice/serializer"

func MarshalDeepStructAsJSON(s *DeepStruct) ([]byte, error) {
    buff := make([]byte, 1, 54)
    buff[0] = '{'
    if s.Deep == nil {
        buff = append(buff, "\"deep\":null,"...)
    } else {
        buff = append(buff, "\"deep\":"...)
        buff = append(buff, '[')
        for _, v := range s.Deep {
            if v == nil {
                buff = append(buff, "null"...)
            } else {
                buff = append(buff, '{')
                for mapKey, mapValue := range v {
                    buff = serializer.AppendSerializedString(buff, mapKey)
                    buff = append(buff, ':')
                    if mapValue == nil {
                        buff = append(buff, "null"...)
                    } else {
                        buff = append(buff, '{')
                        for mapKey, mapValue := range mapValue {
                            buff = serializer.AppendSerializedString(buff, mapKey)
                            buff = append(buff, ':')
                            if mapValue == nil {
                                buff = append(buff, "null"...)
                            } else {
                                buff = append(buff, '{')
                                for mapKey, mapValue := range mapValue {
                                    buff = serializer.AppendSerializedString(buff, mapKey)
                                    buff = append(buff, ':')
                                    if mapValue == nil {
                                        buff = append(buff, "null"...)
                                    } else {
                                        buff = append(buff, '{')
                                        for mapKey, mapValue := range mapValue {
                                            buff = serializer.AppendSerializedString(buff, mapKey)
                                            buff = append(buff, ':')
                                            buff = serializer.AppendSerializedString(buff, mapValue)
                                            buff = append(buff, ',')
                                        }
                                        if buff[len(buff)-1] == ',' {
                                            buff[len(buff)-1] = '}'
                                        } else {
                                            buff = append(buff, '}')
                                        }

                                    }
                                    buff = append(buff, ',')
                                }
                                if buff[len(buff)-1] == ',' {
                                    buff[len(buff)-1] = '}'
                                } else {
                                    buff = append(buff, '}')
                                }

                            }
                            buff = append(buff, ',')
                        }
                        if buff[len(buff)-1] == ',' {
                            buff[len(buff)-1] = '}'
                        } else {
                            buff = append(buff, '}')
                        }

                    }
                    buff = append(buff, ',')
                }
                if buff[len(buff)-1] == ',' {
                    buff[len(buff)-1] = '}'
                } else {
                    buff = append(buff, '}')
                }

            }
            buff = append(buff, ',')
        }
        if buff[len(buff)-1] == ',' {
            buff[len(buff)-1] = ']'
        } else {
            buff = append(buff, ']')
        }

        buff = append(buff, ',')
    }
    if buff[len(buff)-1] == ',' {
        buff[len(buff)-1] = '}'
    } else {
        buff = append(buff, '}')
    }
    return buff, nil
}

*1:because of https://tinygo.org/lang-support/stdlib/#encoding-json

*2:i.e. not a browser runtime

Released sbt-spotless: an sbt plugin for Spotless

github.com

I've released sbt-spotless, this is an sbt plugin for Spotless.

Spotless is currently supporting plugins for maven and gradle, but it seems there had been no sbt's one, so I made this. This plugin works on sbt 1.3.x and legacy sbt 0.13.x *1.

The usage is quite simple. This plugin has already been published the Maven Central, so you just only put a dependency definition into your plugins.sbt:

addSbtPlugin("net.moznion.sbt" % "sbt-spotless" % "0.1.3")

next, configure your build.sbt for code formatter(s):

import net.moznion.sbt.spotless.config._

lazy val root = (project in file("."))
  .settings(
    name := "Example",
    spotlessJava := JavaConfig(
      googleJavaFormat = GoogleJavaFormatConfig("1.7")
    ),
  )

finally, you can run sbt spotlessCheck and sbt spotlessApply commands to check and apply the code formatter.

And – this feature is only available on sbt 1.3.x – you can use checkOnCompile and applyOnCompile options like the following:

lazy val root = (project in file("."))
  .settings(
    name := "Example",
    spotless := SpotlessConfig(
      checkOnCompile = true,
      // applyOnCompile = true,
    ),
  )

then, it checks or applies the code formatter automatically on compiling, automatically.

If you'd like to know configuration in detail, please refer to the following documentation: Configurations · moznion/sbt-spotless Wiki · GitHub

This plugin supports Java, Scala, Kotlin, Groovy, C++, and SQL formatters so far, but Spotless core supports various formatters furthermore. Please feel free to ask me through an issue or pull-request if you feel this should support another formatter.

Enjoy!

*1:but on sbt 0.13.x, the features are limited

Published proxy-protocol-js

(Japanese article is here: https://moznion.hatenadiary.com/entry/2019/04/30/131509 )

I had published a library that is named proxy-protocol-js.

github.com

www.npmjs.com

This library recognizes the PROXY protocol and has responsibilities for building and parsing the protocol for JavaScript and TypeScript (for details of PROXY protocol, please refer to the following the spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt ).

This library's strengths are the following:

  • Supports V1 (text protocol) and V2 (binary protocol)
  • Supports multipurpose functions
    • To build a protocol payload
    • To parse a protocol payload
    • To identify the protocol version according to a protocol payload
  • Better performance than existing implementations
  • Supports TypeScript (i.e. this library is written in TypeScript)
  • No extra dependencies

Enjoy!

gowrtr - a library that supports golang code generation

(Japanese article is here: https://moznion.hatenadiary.com/entry/2019/01/14/111719)

I published a library that supports golang code generation, this named gowrtr (pronunciation: go writer). This library has a number of generators for golang code generation.

github.com

As Synopsis shows you:

package main

import (
    "fmt"

    "github.com/moznion/gowrtr/generator"
)

func main() {
    generator := generator.NewRoot(
        generator.NewComment(" THIS CODE WAS AUTO GENERATED"),
        generator.NewPackage("main"),
        generator.NewNewline(),
    ).AddStatements(
        generator.NewFunc(
            nil,
            generator.NewFuncSignature("main"),
        ).AddStatements(
            generator.NewRawStatement(`fmt.Println("hello, world!")`),
        ),
    ).
        EnableGofmt("-s").
        EnableGoimports()

    generated, err := generator.Generate(0)
    if err != nil {
        panic(err)
    }
    fmt.Println(generated)
}

the above code generates the following go code:

// THIS CODE WAS AUTO GENERATED
package main

import "fmt"

func main() {
        fmt.Println("hello, world!")
}

This library works as above.

This example is really simple so it looks it increases the amount of code should write at first glance, but if you want to generate large-scale code, this library should be useful to do that (please refer to the following topic about immutability).

For more detail, please refer to the GoDoc. The documentation also contains examples.

This library has two peculiarities:

  • It can apply code formatters (i.e. gofmt and/or goimports) to the generated code
  • Each method of the library act as immutable

The first one, it can obtain the advantage that "the format of the generated code is unified" and "can check the syntax of the generated code" by applying gofmt. Moreover, it can get the merit that "there is no necessary to think and write about which library should be imported while writing the generator for code generation" with using goimports.

The second means, each method of the generators doesn't change the internal state implicitly so a user can take a snapshot of the generator on demand. This is useful to reuse and derive the code generator instance.


By the way, there is square/javapoet that is a library supports Java code generation in the Java world. That is a great library. gowrtr is inspired by javapoet.

Perhaps this library lacks features and/or notation supports. If so please feel free to let me know.

Enjoy.

aws-lambda-perl5-layer is now available

I've released aws-lambda-perl5-layer: this is an AWS Lambda runtime layer for Perl5.

github.com

This project makes you to be able to execute Perl5 on AWS Lambda. This project uses AWS Lambda Runtime API that was announced re:Invent 2018. For detail of the API, please refer to the following documentation:

And I've published layers so you can start to use Perl5 Lambda with the provided one right now. Provided ARNs are following:

  • arn:aws:lambda:${REGION}:652718333417:layer:perl-5_26-layer:1
  • arn:aws:lambda:${REGION}:652718333417:layer:perl-5_28-layer:1

Supported regions are following:

  • ap-northeast-1
  • ap-northeast-2
  • ap-south-1
  • ap-southeast-1
  • ap-southeast-2
  • ca-central-1
  • eu-central-1
  • eu-west-1
  • eu-west-2
  • eu-west-3
  • sa-east-1
  • us-east-1
  • us-east-2
  • us-west-1
  • us-west-2

Please see the repository documentation and moznion/aws-lambda-perl5-layer-example for usage.

github.com

Enjoy!

Docuss - A library to test with describing document for controller layer

I released a library "Docuss" for Java (this library requires Java 8 or later).
It is available in maven central.

maven central

This library works as below:

  • This library provides an inspection method to test the HTTP response. This library sends an HTTP request to target URI and it hands over the response to the inspector.
  • When test is passed, this library outputs the contents of the HTTP request and response *1 as any format by arbitrary method.

In short, this library provides a function to test HTTP request and response with describing their contents.
It is similar to autodoc, however docuss is more minimal (or cheaper?) than that.

Fundamental information and usage are written in the README and javadoc, the points that worked out are below;

  • Format of output is controllable
  • Method to output is controllable
  • It doesn't depend on any concrete HTTP client implementation

Format of output is controllable

This library can specify the format of output of documents.
It is easy to do that; you can specify the output format by writing formatter generator which outputs the formatter to convert contents of request and response to arbitrary format. Formatter generator must be implemented DocussFormatterGenerator interface.

Default, this library provides a formatter generator that generates YAML formatter.

Method to output is controllable

This library can specify the destination of the output. The way is just like formats’ one; you can specify the method to output by implementing the interface DocussPresenter.

Default, this library provides two presenters. One is a presenter to output to STDIN, another is a presenter to output to file with appending.

It doesn’t depend on any concrete HTTP client implementation

This library doesn’t depend on any concrete HTTP client implementation so if you want to use specific HTTP client, you can implement DocussHttpClient interface. Basically, the implementation of this interface bears the responsibility to implement the core of functions.

Default, this library supports the simple HTTP client layer implementation which is using Apache httpclient.

Conclusion

I think the way that outputs document from test code is right and natural. I want library to satisfy such function so implemented this.
I usually get out of breath when finished writing the production code and test code so it is difficult for me to write the document.
Even if it is in such a situation, we should write documents firmly so I think such library to support to write documents is necessary.

Enjoy.

*1:in this library, these are called "document"

sprint - Fluent, small and fast string formatter for Java

I published "sprint" version 0.0.2.

And it is also available on maven central. http://search.maven.org/#artifactdetails%7Cnet.moznion%7Csprint%7C0.0.2%7Cjar

Sprint is a string formatter library for Java.
Simple usage is below.

final Sprint sprint = new Sprint();
System.out.Println(sprint.ff("Hello: {}!", "John")); // => Hello: John!

Pros of this formatter library are following.

Fluent template

Sprint takes a fluent (or possibly loose?) template like "{}:{}". {} is a placeholder. This formatter fills arguments into placeholders which are corresponded.
There is no need to mind about correspondence between placeholders of a template and type of arguments. Sprint fills argument with calling Object#toString().

Fast formatting

Sprint parses template to pick up placeholders, and it constructs data structure according to parsed result. This formatter builds a string with scanning this data structure.
Sprint stores this parsed data structure with linking to template in ConcurrentHashMap which is in instance of Sprint, so when formatting with the same template, it uses corresponded pre-parsed data structure.
It means this formatter parses template only at once, so it can reduce redundant template parsing processing.

Result of benchmarking is below (result of jdk1.8.0_92, benchmark code is here). f:id:moznion:20160605223844p:plain

Small

Source code quantity is small and simple :)

Conclusion

Sprint is a fluent, small and fast string formatter for Java.
This formatter is a beta quality, it has potential for growth of performance.
Enjoy.