= Atbash JSON Smart Rudy De Busscher v1.1.1, 04/08/2022 :example-caption!: ifndef::imagesdir[:imagesdir: images] ifndef::sourcedir[:sourcedir: ../../main/java] User manual for Atbash JSON. == Release Notes === 1.1.1 . Capture `NumberFormatException` for ill-formed JSON strings. === 1.1.0 . Support for converting JSON Array to Java `Set`. === 1.0.0 Rewrite `accessors-smart` part to remove the dependency on ASM and usage of `setAccessible` so that it runs on newer JDK versions. Breaking changes . Primitives are no longer supported in some cases of getter and setter usage. They are still supported when using public properties in classes. . The package of `FieldPropertyNameMapper` is changed. . The package of `FieldFilter` is changed. . `BeanAccessConfig` is removed and also the possibilities to map Classes and Fields (other alternatives still exist) === 0.9.2 . Support for creating instances of immutable class through the builder pattern. === 0.9.1 . Support for @JsonProperty to define JSON property name . SPI for defining JSON property name . Integrated access-smart for additional features Breaking changes . Due to integration of _access-smart_, some internal classes are using now other dependent class names (to Atbash package names). But should not affect developer code which only uses @PublicAPI. === 0.9.0 . Initial release, based on the code of JSON smart v2. . Support for @MappedBy for easy customized mapping logic. . Support for _top-level_ Parameterized Types . Optimization for use within Octopus JWT == Introduction https://github.com/netplex/json-smart-v2[JSON Smart v2], is a small fast library for converting POJO to and from JSON in Java 7. When creating the JSON and JWT support within Octopus, I found out it was difficult to have custom logic for a few cases. That was the reason to start from the original code and tailor it to my needs (since the library itself is no longer maintained). == Standard usage ---- T JSONValue.parse("", Class); ---- Converts the "" string to instance of T. ---- List data = (List) JSONValue.parse("", new TypeReference>(){}); ---- Converts the to instance of List containing Bean instances. Without the TypeReference information, Atbash JSON is only capable of creating a _List_ of _JSONObject_. ---- List data = JSONValue.parse("", ArrayList.class); ---- ---- String JSONValue.toJSONString(T); ---- Converts the POJO T to a JSON String. Any method that parses JSON string can throw a `ParseException`. It is a good practice to catch the `AtbashException` at a general location in your program to avoid any exception propagation to the end user. == Customizations === Define JSON property name By default, the field name is used as property name within JSON. However, there are scenarios that you need to create a Java class for a JSON (or JWT payload) which needs to comply with some externally defined names. These property names are probably not valid Java property names according to your naming standard. The solution is to specify the fields with the @JsonProperty (be.atbash.json.annotate.JsonProperty) annotation. The value of this annotation determine the JSON property name. A Java class defined ---- public class Foo { @JsonProperty("property") private String stringValue; private int intValue; // Getters and setters for properties } ---- will result in a JSON like this ---- {"property":"value1","intValue":123} ---- === SPI for defining JSON property name Atbash JSON has also an SPI available to support multiple ways of defining the property name. Key here is the interface _be.atbash.json.asm.mapper.FieldPropertyNameMapper_. This interface is used to determine the property name for a field. ---- public interface FieldPropertyNameMapper { String getPropertyName(Accessor accessor); } ---- So implement this interface, and define the classname within a service loader definition file (within /META-INF/services/be.atbash.json.asm.mapper.FieldPropertyNameMapper) When you are not able to determine the property name (based on additional annotations or some general mapping rule based on the field name), just return _null_. You give then the opportunity that other implementations of this interface come up with a name or that the default (property name = field name) will be used. === Custom JSON creation The creation of the JSON can be customized in 2 ways. You can implement the interface _be.atbash.json.JSONAware_ or define a custom _Writer_ with the _@MappedBy_ annotation. ---- public interface JSONAware { /** * @return JSON text */ String toJSONString(); } ---- The result of the _toJSONString()_ method will be added to the JSON output. One can make use of the _JSONObject_ class to help in the creation of JSON Strings, ---- public String toJSONString() { JSONObject result = new JSONObject(); result.put("key1", key1); result.put("key2", key2); result.put("key3", key3); for (Map.Entry entry : additional.entrySet()) { result.put(entry.getKey(), entry.getValue()); } return result.toJSONString(); } ---- You need to make sure that you serialize the complete object tree to JSON. Another option, but very similar, is to use an annotation to indicate the code which needs to be called when the Object needs to be Serialized to JSON. This way, the code to create the JSON can be kept out of the class itself. Annotate the Object with _be.atbash.json.parser.MappedBy_ and specify the Writer within the _writer()_ member. ---- @MappedBy(writer = PriceJSONWriter.class) ---- and ---- public class PriceJSONWriter implements JSONWriter { @Override public void writeJSONString(E value, Appendable out) throws IOException { out.append(String.format("\"%s%s\"", value.getValue(), value.getCurrency().toJSONString())); } } ---- In this example, the Currency object implements the _JSONAware_ interface. The last option discussed here, is to register the JSONWriter within the system, as follows ---- JSONValue.registerWriter(MyColor.class, new MyColorWriter()); ---- Then the writer is picked up whenever you ask for converting the MyColor class in this example to JSON. === Custom reading of JSON The conversion from JSON to an object instance can be customized by encoders which can be defined with _@MappedBy_. The most generic way is to use an implementation of _be.atbash.json.parser.CustomJSONEncoder_ ---- public interface CustomJSONEncoder { T parse(Object data); } ---- The data parameter is most of the time an instance of String, but can be any primitive, JSONArray or JSONObject in case the JSON is malformed or has wrong contents (other contents then expected). There is a special encoder available, _be.atbash.json.writer.CustomBeanJSONEncoder_, which tries to use the setters if they are available, or call the _setCustomValue()_ method otherwise. An example can be seen at the test class _be.atbash.json.testclasses.Token_ and _be.atbash.json.testclasses.TokenJSONEncoder_. An implementation of this interface or the class, needs a no argument constructor. Both classes needs to be specified by a @MappedBy annotation, _encoder()_ member for the simple CustomJSONEncoder implementation, _beanEncoder()_ member for CustomBeanJSONEncoder class. Another customization is possible by registering encoders into the system itself, and then they don't need to be defined by a _mappedBy_ annotation. (It has more flexibility but is more difficult) In the case where the bean is an immutable instance, not default no argument constructor and no setters, there is a specific CustomBeanJSONEncoder available called +CustomBeanBuilderJSONEncoder+. Subclasses of +CustomBeanBuilderJSONEncoder+ can also be defined as the value of the _beanEncoder_ member of the @MappedBy annotation. Besides the class which will be created, a Bilder class needs to be defined also. ---- public class ImmutableBeanJSONEncoder extends CustomBeanBuilderJSONEncoder { ---- This specific encoder must implements 2 methods ---- void setBuilderValue(U builder, String key, Object value); T build(U builder); ---- The _setBuilderValue_ needs to call the corresponding method on the builder for the key property read from the JSON. The _build_ method should then create the instance of the requested class, most likely by calling the _build()_ method of the builder. Start by extending the ++JSONEncoder++ class and register it by ---- JSONValue.registerEncoder(.class, new CustomEncoder()); JSONValue.registerEncoder(new TypeReference>() {}, new CustomEncoder()); ---- The second statement is for registering a Typed reference. This workaround is required to compensate for the Type erasure which is performed by Java. After registering, this encoder are used when you ask to _parse_ a certain String to the specified type. The test classes have examples if you want to use this type of customization. == @JsonIgnore When a field is marked with the ++@JsonIgnore++ annotation, it is ignored during the encoding and decoding process. It can also be used in combination with the @MappedBy.beanEncoder feature. Such a field (which is annotated with @JsonIgnore) will not handled by the default bean encoder when the JSON property key and field name matches, but the value will always be passed to the _setValue()_ of the CustomBeanJSONEncoder.