Git Product home page Git Product logo

jackson-dataformat-hal's Introduction

Overview

This project contains a Jackson extension component to support the HAL JSON format both with respect to serializing and deserializing.

The goal is to handle HAL links and embedded objects as POJO properties with a data-binding similar to the normal Jackson JSON handling.

Status

Module is considered production ready.

Maven Central Javadoc Build status Coverage Status Known Vulnerabilities

Usage

The extension comes with a few annotations to mark the HAL links and embedded objects as well as a class to handle the HAL link specification. A POJO used for HAL formatted JSON could look as follows.

@Resource
class Model {
    String modelProperty;

    @Link
    HALLink self;

    @Link("rel:associated")
    HALLink relation;

    @EmbeddedResource
    AssociatedModel associated;
}

@Resource
class AssociatedModel {
    String associatedProperty;

    @Link
    HALLink self;
}

The @Resource annotation marks the POJO as a HAL based resource. The @Link and @Embedded annotations marks links to go into the _links object and embedded resources to go into the _embedded object respectively. HAL links must be defined using the HALLink class as this models the properties defined in the HAL specification. Both @Link and @Embedded fields may be collections or arrays.

The JSON resulting from the above small POJO model would look like the following.

{
    "_links": {
        "self": { "href": "https://..."},
        "rel:associated": { "href": "https://..."}
    },
    "_embedded": {
        "associated": {
            "_links": {
                "self": { "href": "https://..." }
            },
            "associatedProperty": "..."
        }
    },
    "modelProperty": "..."
}

All fields which are not annotated will be handled by the normal Jackson JSON data-binding.

Including Curies

@Resource
@Curie(curie = "curie", href = "http://example.com/doc/{rel}")
class Model {
    String modelProperty;

    @Link
    HALLink self;

    @Link("rel:associated")
    HALLink relation;

    @Link(value = "curieLink", curie = "curie")
    HALLink curieLink;

}
@Resource
@Curies({
    @Curie(href = "http://docs.my.site/{rel}", curie = "curie1"),
    @Curie(href = "http://docs.other.site/{rel}", curie = "curie2")})

class Model {
    String modelProperty;

    @Link
    HALLink self;

    @Link("rel:associated")
    HALLink relation;

    @Link(value = "curieLink11", curie = "curie1")
    HALLink curieLink11;

    @Link(value = "curieLink21", curie = "curie2")
    HALLink curieLink21;

    @Link(value = "curieLink22", curie = "curie2")
    HALLink curieLink22;

    @Link(value = "curieLink23", curie = "curie2")
    HALLink curieLink23;

}

The resulting JSON would be:

{
    "_links": {
        "curies": [{
            "name": "curie1",
            "href": "http://docs.my.site/{rel}",
            "templated": "true"
        },{
            "name": "curie2",
            "href": "http://docs.other.site/{rel}",
            "templated": "true"
        }],
        "self": { "href": "https://..."},
        "rel:associated": { "href": "https://..."},
        "curie1:link11": { "href": "https://...", "templated" : "..."},
        "curie2:link21": { "href": "https://...", "templated" : "..."},
        "curie2:link22": { "href": "https://...", "templated" : "..."}
    },
    "_embedded": {
        "associated": {
            "_links": {
                "self": { "href": "https://..." }
            },
            "associatedProperty": "..."
        }
    },
    "modelProperty": "..."
}

The above is equivalent to:

{
    "_links": {
        "self": { "href": "https://..."},
        "rel:associated": { "href": "https://..."},
        "http://docs.my.site/link11": { "href": "https://...", "templated" : "..."},
        "http://docs.other.site/link21": { "href": "https://...", "templated" : "..."},
        "http://docs.other.site/link22": { "href": "https://...", "templated" : "..."}
    },
    "_embedded": {
        "associated": {
            "_links": {
                "self": { "href": "https://..." }
            },
            "associatedProperty": "..."
        }
    },
    "modelProperty": "..."
}

Both will be supported for deserialization. Also if curie prefixes in the incoming document is chosen to be different from the prefixes in the POJO annotations deserialization will be supported.

Serializing POJOs as HAL JSON

Serialization is similar to the normal JSON serialization using the HALMapper instead of the ObjectMapper.

ObjectMapper halMapper = new HALMapper();
String json = halMapper.writeValueAsString(new POJO());

Deserializing POJOs from XML

Deserialization is also similar to the normal JSON handling using the HALMapper.

ObjectMapper halMapper = new HALMapper();
POJO value = halMapper.readValue("{..json..}", POJO.class);

Using HAL Extension in JAX-RS Application

To use the HAL extension in a JAX-RS application you can add the jackson-jaxrs-providers module to your application and use this to ensure the HALMapper is being used.

@ApplicationPath("/")
public class MyApplication extends Application {
    private final Set<Object> singletons = new HashSet<>();

    public MyApplication() {
        singletons.add(new JacksonJsonProvider(new HALMapper()));
    }

    @Override
    public Set<Object> getSingletons() {
        return Collections.unmodifiableSet(singletons);
    }
}

Using HAL Extension in JAX-RS Client

Using the HAL extension in a JAX-RS client works very similar to using it in an application. The jackson-jaxrs-providers can be used to register the HALMapper.

ClientBuilder cb = ClientBuilder.newBuilder();
Client client = cb.register(new JacksonJsonProvider(new HALMapper())).build();

HALLink extended with temporal aspect

The HALLink is extended with a seen property. This is beyond the current specification of HAL. An addendum or change to HAL will be proposed to incoorporate the seen property in the _Link specification. The purpose of the seen property is to give consumers of a service the ability to judge for themselves whether the last time a HAL related object was seen either as a link or as an embedded object.

Knows Limitations

The Wildfly application server comes with both a Jackson and Jackson 2 module which takes precedence over the ones registered with the JAX-RS client. Thus to make sure the HAL extension is actually used when creating a JAX-RS client running inside Wildfly you need to exclude the Jackson modules in the jboss-deployment-structure.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <exclusions>
            <module name="org.jboss.resteasy.resteasy-jackson-provider"/>
            <module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>

jackson-dataformat-hal's People

Contributors

allanhoejgaardjensen avatar arucard21 avatar dhenneke avatar eriksidelmannjensen avatar langecode avatar snyk-bot avatar timothy-volvo 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  avatar  avatar  avatar

jackson-dataformat-hal's Issues

ResourceSupport<T>, ResourceCollectionSupport<T>

Here is an example how to add HAL resource support to an existing application where you already have a large set of domain entities.

The trick is to use a @JsonUnwrapped around the existing entity. You have a large set of existing classes that you don't want to / or cannot modify. Then, with ResourceSupport and ResourceCollectionSupport it becomes easy to add the findAll(), findOne(ID id) features.

The Supporting Cast

@Resource
public abstract class ResourceSupport<T> {

    @Link
    public final HALLink self;

    @JsonUnwrapped
    public final T domain;

    public ResourceSupport(Link self, T domain) {
        this.self = self;
        this.domain = domain;
    }
}
@Resource
public abstract class ResourceCollectionSupport<T> {

    @Link
    public final HALLink self;

    @EmbeddedResource
    public final Collection<ResourceSupport<T>> content;

    public ResourceCollectionSupport(Link self, Collection<ResourceSupport<T>> domain) {
        this.self = self;
        this.content = domain;
    }
}

The Boilerplate

    public static class Foo {
        public UUID id = UUID.randomUUID();
        public String foo = "bar";
    }

    public static class Bar {
        public BigInteger bar = BigInteger.ZERO;
    }

    public static class FooResourceSupport extends ResourceSupport<Foo> {

        public FooResourceSupport(Foo foo) {
            super(new Link.Builder("/foo/" + foo.id.toString()).build(), foo);
        }
    }

    public static class BarResourceSupport extends ResourceSupport<Bar> {

        public BarResourceSupport(Bar bar) {
            super(new Link.Builder("/bar/" + bar.bar).build(), bar);
        }

        @EmbeddedResource(value = "thisIsTheDimensionsObject")
        public FooResourceSupport dimensions;
    }

    public static class FooCollectionResource extends ResourceCollectionSupport<Foo> {

        public FooCollectionResource(Collection<Foo> domain) {
            super(new Link.Builder("/foo").build(), domain.stream().map(FooResourceSupport::new).collect(Collectors.toList()));
        }
    }

Add CURIE support for embedded resources

In my reading of the HAL-draft it is perfectly valid to use CURIE syntax for the link relation type of embedded properties. To serialize the following "hypertext cache pattern" JSON:

{
  "_links": {
    "curies": [
      {
        "href": "http://example.org/relations/foo/{rel}",
        "templated": true,
        "name": "foo"
      }
    ],
    "foo:bars": [
      {
        "href": "http://example.org/bars/248875bd-c7e1-357f-8473-450cc49a8308"
      }
    ],
    "self": {
      "href": "http://example.org/bars"
    }
  },
  "_embedded": {
    "foo:bars": [
      {
        "_links": {
          "self": {
            "href": "http://example.org/bars/248875bd-c7e1-357f-8473-450cc49a8308"
          }
        },
        "uuid": "248875bd-c7e1-357f-8473-450cc49a8308"
      }
    ]
  },
  "count": 1
}

Currently someone needs to provide a fixed CURIE value for the embedded resource:

@Resource
@Curie(prefix = "foo", href = "http://example.org/relations/foo/{rel}"
public class Bars {

  private HALLink self;
  private List<Bar> barResources;
  private List<HALLink> barLinks;
  private Integer count;

  public (HALLink self, List<Bar> bars) {
    this.self = Objects.requireNonNull(self);
    this.barResources = Objects.requireNonNull(bars);
    this.barLinks = bars.stream().map(bar -> bar.getSelf()).collect(Collectors.toList());
    this.count = Integer.valueOf(bars.size());
  }

  @Link
  public HALLink getSelf() {
    return this.self;
  }

  @EmbeddedResource(value = "foo:bars")
  public List<Bar> getBarResources() {
    return this.barResources;
  }


  @Link(curie = "foo", value = "bars")
  public List<HALLink> getBarLinks() {
    return this.barLinks;
  }

  public Integer getCount() {
    return this.count;
  }

}

It would be nice if someone could use @EmbeddedResource(curie = "foo", value = "bars") (as with @Link).

Add support for @JsonView

Annotating a class with @Resource breaks @JsonView behavior. Would be very nice, if both work together. Not only for state properties, but also for links and embedded resources.

is there any way to get all links?

i have a resource which will have different links depending on the server side configuration. i want to be able to get all links into a list of Hallink objects. i dont know the link names at build time, but they do match a pattern.

Update documentation/readme

Documentation seems somewhat outdated.

  • Link to the spec draft returns 404. Should be https://tools.ietf.org/html/draft-kelly-json-hal-08
  • Sample code and description contains @Embedded. Should be updated to @EmbeddedResource. This was very confusing when i discoverd this project.
  • Sample code contains simple HALLink fields only. Should show that e.g. Collection<HALLink> is supported as well. Only got this by looking into the code.

CURIE Provider

Thinking about how we might make CURIE generation simpler for this library rather than have to specify them for all of our resources. Thought I'd throw the following idea out there for discussion....

1: Support optional CurieProvider interface to be provided when instantiating HALMapper.
2: CurieProvider interface used in serializer. When a link of the form "foo:bar" detected (contains colon) then CURIE automatically generated by CurieProvider implementation.

This would allow CurieProvider class to turn these three links "foo:bar", "foo:bar2" and "flibble:flip" links into e.g.

"_links": {
"curies": [
{
"name": "foo",
"href": "http://my.api.com/docs/rels/foo-{rel},
"templated": true
},
{
"name": "flibble",
"href": "http://my.api.com/docs/rels/flibble-{rel},
"templated": true
}
]
}

Note that I never had to specify the curies link in my resource class, it just showed up during serialization. It was summoned into existence upon detection of links containing a colon, the serializer then delegated to a CurieProvider implementation.

Thought I'd share the idea in case there is merit in it. Happy to scratch a PR if there is. Feel free to close if you feel like this is a dead-end however.

Support Jackson 2.10.0

I've upgraded to Spring Boot 2.2.0 which uses Jackson 2.10.0 but when I do so I get an error when the HAL resources are serialized:

Caused by: java.lang.IllegalStateException: null
	at com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer.valueToString(NumberSerializer.java:161)
	at com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer.isEmpty(NumberSerializer.java:132)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:711)
	at io.openapitools.jackson.dataformat.hal.ser.HALBeanSerializer$FilteredProperties.serialize(HALBeanSerializer.java:191)

I suspect that there's some change in Jackson 2.10 which makes jackson-dataformat-hal incompatible with this version.

Allow non templated String links

Currently, whenever you create a HALLink from a String with the Builder, the templated field is set.

I don't have templated links but they are only relative paths and therefore not valid URIs. However, I also don't want to always send the templated field. What would be the best way to achieve this?

GitHub Actions broken

It seems something is wrong with the GitHub Actions. The build does not seem to start for any of the pull requests. It seems that something needs to be changed in the protected branch settings of the repository:
https://github.community/t/expected-waiting-for-status-to-be-reported/16727

Hope you can fix the settings this since it's blocking any PRs from being merged (by me at least).

Edit: @langecode looks like you can still merge the PRs. So it's less of a problem. Might still want to look into this though.

Conflict with JsonTypeInfo for Polymorphic Classes

Problem:
We are using polymorphic classes in our API.
Unfortunately the HAL structure don't get created when its come to serialization.
We tried every possible combination of the @JsonTypeInfo annotation and nothing "really" changed.
Maybe you have an Idea or a Solution.
Currently we can't use your Library as it proposed to.

Deserialization is not effected so far.
Related Pull Request: #24
Pipeline is failing as expected, because I added all possible cases for the polymorphic classes

HALLink should be Serializable

There are many cases in which one want to cache the API results, which is currently not as easy as it could be. To work around the missing Serializable implementation I had to write custom serialization classes for HALLink.

Since caching of API results is a very common and implementation is really easy, I recommend making HALLink implement Serializable.

Forked from my account

I'm not sure why the openapi-tools repo was forked from my account. But since the openapi-tools repo should probably be the main repo for this, should I just transfer ownership of my repo?

I think that should remove the "forked from" message, though I'm not quite sure. But it shouldn't be too hard to find a way to do this.

HAL Forms Support

Thanks for a fantastic tool already. Seems like this is growing in popularity and wouldn't be a big lift to add it to the existing library: https://rwcbook.github.io/hal-forms/

Wondering what your thoughts on that are? Would you be opposed to pull requests adding this feature or do you see it as permanently out-of-scope for this library?

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.