All Projects → alexheretic → dynamics

alexheretic / dynamics

Licence: Apache-2.0 license
Java library for handling nested dynamic data

Programming Languages

java
68154 projects - #9 most used programming language

Dynamics Build Status Coverage Status Maven Central

Dynamics is a Java library for handling nested weakly-typed data in a fluent and null-safe way.

Initially developed to help handle JSON and XML messages in a direct way without tedious and repetitive null checking, type conversion & casting. It has become a powerful & productive way of directly dealing with dynamic data without the horribleness that can typically involve.

Weakly-Typed Nested Data Structure Handling

Map nestedMap = ...
// {product={investment={investment-1=12345.33, investment-2=43213.44}, effective=2015-03-07T00:35:11}}

Dynamic.from(nestedMap)
    .get("product")
    .get("effective")
    .convert().intoLocalDateTime(); // java.time.LocalDateTime 2015-03-07T00:35:11

In Detail

An alexh.weak.Dynamic object is a weakly-typed, possible nested structure that allows null-safe child selection, creating the Dynamic wrapper is easy

Dynamic message = Dynamic.from(nestedMap);

Each 'get' call returns a non-null Dynamic instance that represents the child with that key, or the key's absence. Finally the wrapped object target can be accessed with asObject(), or as(Class) to cast.

Dynamic investment1 = message.get("product").get("investment").get("investment-1"); // null & exception safe
investment1.isPresent(); // true
investment1.as(BigDecimal.class); // 12345.33, assuming it is a BigDecimal

This allows a single call to isPresent() to indicate if the required value exists.

message.get("product").get("one").get("two").get("three").get("four").isPresent(); // false

With String keys we can fetch deeply in a single get call by splitting the string into multiple gets and supplying a splitter string. In addition to isPresent() method a Dynamic my be unpacked into an Optional with asOptional() or can be wrapped into an OptionalWeak and handled fluently with maybe()

message.get("product.investment.investment-2", ".").maybe().as(BigDecimal.class);
// Optional[43213.44]

For '.' character splitting Dynamic#dget(String) method is supplied for convenience

message.dget("product.investment.investment-2").maybe().as(BigDecimal.class);
// Optional[43213.44]

Error Describing

Dynamic instances throw descriptive exceptions when data is missing, or not as selected.

// exception message:
// "'holdings' key is missing in path root->product->*holdings*->foo->bar,
//   available root->product: Map[effective, investment]"
message.dget("product.holdings.foo.bar").asObject(); // throws

Type Conversion

Aiding permissive reading (desired in servers consuming external messages) Dynamics provides common usage type runtime conversions with the alexh.weak.Converter class.

Converter.convert("1234.4321").intoDecimal(); // BigDecimal 1234.4321
Converter.convert(1234.4321d).intoDecimal(); // BigDecimal 1234.4321 (approx)

Usage is also built into Dynamic instances.

message.get("product").get("effective").convert().intoLocalDateTime();
// LocalDateTime of 2015-03-07T00:35:11
message.get("product").get("effective").maybe().convert().intoLocalDateTime();
// Optional<LocalDateTime>[2015-03-07T00:35:11]

XML Dynamics

XML documents can be wrapped in an XmlDynamic which acts like a normal dynamic with string keys & values but with some special features

<product>
  <investment id="inv-1">
    <info>
      <current>
        <name>some name</name>
      </current>
    </info>
  </investment>
  <investment id="inv-2" />
</product>

We can select the nested 'name' element value just like a normal Dynamic

Dynamic xml = new XmlDynamic(xmlMessage); // 'xmlMessage' is a string of the above xml
xml.get("product.investment.info.current.name", ".").asString(); // "some name"

Also since XML has certain key name restrictions the pipe character '|' can be used as a splitter without declaration

xml.get("product|investment|info|current|name").asString(); // "some name"

Attributes can be accessed in exactly the same way as elements, or explicitly

xml.get("product|investment|id").asString(); // "inv-1"
xml.get("product|investment|@id").asString(); // "inv-1"

Multiple child elements with the same local-name effectively have [i] appended to them where i is their index

xml.get("product|investment|id").asString(); // "inv-1"
xml.get("product|investment[0]|id").asString(); // "inv-1"
xml.get("product|investment[1]|id").asString(); // "inv-2"

Namespaces are ignored by default, but can be used explicitly using the "::" key separator

<ex:product xmlns:ex="http://example.com/example">
  <message>hello</message>
</ex:product>
xmlWithNamespaces.get("product|message").asString();
xmlWithNamespaces.get("http://example.com/example::product|none::message").asString();
// both return "hello"

Dynamics is licensed under the Apache 2.0 licence.

Releases

4.0 is the latest release, available at maven central. Requiring JDK 1.8 or later.

<dependency>
  <groupId>com.github.alexheretic</groupId>
  <artifactId>dynamics</artifactId>
  <version>4.0</version>
</dependency>

Changelog

Release 4.x

  • Add enhanced ClassCastException messages

Release 3.x

  • Add Dynamic#allChildren(), #allChildrenDepthFirst(), #allChildrenBreadthFirst() deep child streaming
  • Add XmlDynamic.hasElementName(String) namespace support
  • 3.1
    • Prevent rare XmlDynamic thread safety issues

Release 2.x

  • java.util.Collection instance support, ie Sets now have children
  • Minor format change to error messages
  • 2.1
    • Fix conversions of some zoned iso strings into ZonedDateTime instances
  • 2.2
    • Improve java.sql.Timestamp style toString date times to properly convert with nanosecond accuracy
  • 2.3
    • Fix thread-safety issue with XmlDynamic#children() traversal
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].