Permalink
Fetching contributors…
Cannot retrieve contributors at this time
780 lines (601 sloc) 33.9 KB

YaGson User Guide

Overview

YaGson is a universal type-preserving Java serialization library that can convert (almost) arbitrary Java Objects into JSON and back, with transparent support for circular references of any kind and with a full Java 9 compatibility.

It is based on Gson, inheriting all benefits and adding new valuable features. The name YaGson is actually an abbreviation for "yet another Gson".

Compared to other JSON serializers, such as Gson or Jackson, YaGson imposes almost no restrictions on the serialized classes, and requires neither changes to those classes (like adding annotations for Jackson) nor custom Gson adapters for out-of-the-box support of complicated cases like:

  • child to parent references, self-references, and circular references of any other kind

  • mixed-type arrays, collections, and objects

  • inner, local, and anonymous classes.

Thanks to the above versatility, YaGson is a solid choice for implementing REST interfaces between Java server and client machines, e.g. for Java-based microservices.

Requirements and limitations

YaGson works on Java SE 6 or later, including Java 8.

No third-party dependencies are required. Any other mapping library (including original Gson) may be safely added to the classpath.

Limitations

Although YaGson has much less limitations than other popular JSON mappers, there is still a number of restrictions that cannot be avoided:

  • all classes used during serialization MUST be available during de-serialization. Moreover, having the same versions of the classes is recommended. Although YaGson can work around slight differences between classes, there is no guarantee of correctness. For example, if the name of a class (e.g. an anonymous one) is changed, there is no way to handle such a change correctly. In particular, this limitation means that:

    • the same JRE version is recommended for use on serialization and de-serialization sides;

    • dynamic proxy classes cannot be de-serialized;

  • by default, YaGson resolves classes by name using either the context class loader (if present), or a simple Class.forName(). However, it is also possible to add custom class loaders using YaGsonBuilder.setPreferredClassLoaders(). In that case, the classes are searched in the preferred class loaders first, then in the default class loaders. Anyway, all serialized classes MUST be available in either of these class loaders or the current defining class loader.

  • no ClassLoaders are de-serialized;

  • no external non-Java information, like system file handles or sockets, is serialized. So entities such as SocketInputStream or FileReader cannot be correctly de-serialized;

  • by default, static fields are not serialized. If a class relies on some pre-defined static data (which would be a very bad coding style!), it may fail to function correctly;

  • the return values of the default hashCode() and equals() methods, as defined in java.lang.Object, may differ for two identically constructed instances. So any code relying on object identity or hash code equality may fail. In other words, never use such checks for classes that don’t override hashCode and equals!

  • if a YaGson mapper with non-default settings (i.e. a mapper created using YaGsonBuilder) was used during serialization, then similar mapper settings shall be used during de-serialization;

  • thread-related objects, like Thread or ThreadGroup, are correctly de-serialized only if the thread with the same name and path (in thread groups hierarchy) is found.

Using YaGson

Download

In Maven projects, just add the following dependency:

  <dependency>
    <groupId>com.gilecode.yagson</groupId>
    <artifactId>yagson</artifactId>
    <version>0.4</version>
    <scope>compile</scope>
  </dependency>

Otherwise, download the latest release at GitHub and add yagson-version.jar to the application classpath.

Creating a YaGson Instance

Similarly to Gson, a mapper instance with default settings may be created using just new YaGson(). The non-default settings may be specified if the mapper is created using YaGsonBuilder:

  YaGson mapper = new YaGson(); // a mapper with default settings
  YaGson altMapper = new YaGsonBuilder()
      .setPrettyPrinting()
      .create(); // a mapper with custom settings

See the original Gson’s documentation or the source code for the list of available custom settings. Some additional YaGson-related settings are described in the sections below.

General Mapping Principles

All Java objects, arrays and values of primitive types are mapped to JSON entities: JSON Objects ({…​}), JSON Arrays ([…​]) and JSON literals. Which Java type is mapped to which JSON entity depends on many factors, including

  • the current settings of the given YaGson mapper instance;

  • the actual type/class of the serialized object;

  • whether the (de-)serialization type is known and matches the actual runtime class;

  • whether the current serialized object has already been visited (e.g. when handling cyclic graphs);

  • whether the serialized value is null.

Although neither Java identity or Object.equals() kinds of equality between an arbitrary serialized object and its de-serialized copy can be guaranteed, the default YaGson mapper still provides some natural kind of equality, which is, in simple words, the equality of the types, content, and behavior. The exact definition of such m-equality is somewhat complicated, so feel free to skip it:

Note

The serialized object and its de-serialized copy are m-equal (s=md) if and only if

  • null values are mapped to null

  • ClassLoaders are also mapped to null

  • for any serialized object not mapped to null, the type/class of the de-serialized copy is exactly the same as of the original

  • for primitive types, s==d

  • for Strings, s.equals(d)

  • for Collections and arrays, the sizes of s and d are equal, and for each element of the original collection es, there is a corresponding m-equal element ed in the de-serialized copy, where es=med

  • for ordered collections, such as List, SortedSet, Queue, array, etc., the order of elements is preserved

  • for Maps, the sizes of s and d are equal, and for each key/value pair of the original map, there is a corresponding m-equal key/value pair, where es.key=med.key and es.value=med.value

  • for ordered Maps, like SortedMaps or LinkedHashMap, the order of entries is preserved

  • for a general Object that is not a collection, map, or instance of a special class such as Thread, ClassLoader, WeakReference, etc., all non-static fields (including the transient fields!), are m-equal to the corresponding fields in the deserialized copy

  • (for most of the special classes, the behaviour and content is preserved where possible, but no guarantees are given.)

For example, consider the most common case, which is a serialization of a custom non-collection class:

  // ...
  Person obj = new Person("John", "Doe");

  String objJson = mapper.toJson(obj, Person.class);
  // objJson = {"name":"John","family":"Doe"}

  Person deserializedObj = mapper.fromJson(objJson, Person.class);
  // deserializedObj = Person{name='John', family='Doe'}

As you can see, such objects are mapped to JSON Objects and back on a field-by-field basis. If the class has a well-written equals() method which compares these fields, s.equals(d) would return true. Otherwise, only the type and per-field equality are maintained.

Type Information

As shown in the previous example, a user must supply type information (in the form of the Person.class parameter above) both as the serialization type (to method toJson()), and the de-serialization type (to method fromJson()).

For objects of a non-generic type, just use the class of the object being serialized. For generic types, it is recommended to provide fully parameterized serialization/deserialization types using Gson’s TypeTokens, like

    Type myMapType = new TypeToken<HashMap<Long, String>>(){}.getType();
    String myMapJson = mapper.toJson(myMap, myMapType);
Warning
The serialization type used in toJson, MUST BE equal to or less specific than the de-serialization type used in fromJson. If the de-serialization type is not known at the time of serialization, just use Object.class.

There is a number of cases when the type information provided by the serialization type is not sufficient, for instance:

  • when the de-serialization type is not known, and so Object.class is used as the serialization type;

  • when the actual type of an object to serialize is not exact, i.e. the object is polymorphic, like PersonEx extending Person, with the known type to be just Person;

  • when a mixed-type collection, array, or map is serialized, like ArrayList<Number> with a mix of Integers and Longs;

  • when a type of a field in the serialized class, or of an element in the serialized array, collection, or map is not exact, like in class ObjectHolder { Object obj; }

In all such cases, YaGson automatically emits a @type/@val wrapper around the JSON representation of an entity with inexact or missing serialization type:

   String objJson = mapper.toJson(obj, Object.class);
   // objJson = {"@type":"samples.Person","@val":{"name":"John","family":"Doe"}}


Although emitting of type information may be disabled (e.g. if you only use YaGson to handle circular references), it is strongly not recommended. For instance, if the first and second occurrences of a self-referencing collection in its serialized representation have different formal types (e.g. Collection<Object> and ArrayList<Object>), de-serialization would fail if no type information was emitted.

However, you can always test it with your own data. In order to disable type information emitting, use TypeInfoPolicy.DISABLED when creating the mapper instance:

  YaGson mapper = new YaGsonBuilder()
       .setTypeInfoPolicy(TypeInfoPolicy.DISABLED)
       .create();

References and Reference Policies

Usually, a serialized object contains references to other objects, which shall be serialized too: array or collection elements, map entries and object fields. These objects, in turn, may contains references too. Thus, actually a whole graph of objects is being serialized.

If that graph is cyclic, e.g. one of the child elements contains a reference to the root or the parent element, then most other JSON serializers will throw java.lang.StackOverflowError on an attempt to serialize such a graph.

Quite the contrary, YaGson automatically detects such cyclic references and serializes them as special string literals in either the "@root[.path_from_root_object]" or "@.sibling_element" format.

For example:

  Object[] obj = new Object[3];
  obj[0] = "foo";
  obj[1] = obj;
  obj[2] = "bar";

  String objJson = mapper.toJson(obj, Object[].class);
  // objJson = ["foo","@root","bar"]

  Object[] deserisalizedObj = mapper.fromJson(objJson, Object[].class);
  // deserisalizedObj = [foo, [...], bar]

  ObjectMapper jacksonMapper = new com.fasterxml.jackson.databind.ObjectMapper();
  jacksonMapper.writeValueAsString(obj); // throws StackOverflowError!

Moreover, YaGson can detect not only cyclic references, but also other duplicate objects in the serialization graph, with intentional exclusion of "simple" types like String, primitive types, Numbers etc.

For example:

  Person p = new Person("John", "Doe");
  Person[] obj = new Person[]{p, p};

  String objJson = mapper.toJson(obj, Person[].class);
  // objJson = [{"name":"John","family":"Doe"}, "@.0"]

  Person[] deserisalizedObj = mapper.fromJson(objJson, Person[].class);
  // deserisalizedObj = [Person{name='John', family='Doe'}, Person{name='John', family='Doe'}]


Notice that the second array element, which is a duplicate of the first element, was serialized as the "@.0" reference string literal.

Detection of such duplicates is enabled by default, as many standard Java library classes contain references to "duplicate" objects and rely on their identity. (For example, see java.util.Collections#SynchronizedSortedMap.)

In order to alter the level of duplicate detection, use a non-default ReferencesPolicy while creating the mapper instance:

 YaGson mapper = new YaGsonBuilder()
      .setReferencesPolicy(ReferencesPolicy.CIRCULAR_ONLY)
      .create();


The following reference policies are currently available:

ReferencePolicy Description

DISABLED

No references are detected at all. Vulnerable to StackOverflowException

CIRCULAR_ONLY

Only detects circular references. Prevents StackOverflowException, but the behavior of classes relying on the identity of duplicate objects may be corrupted

CIRCULAR_AND_SIBLINGS

Detects circular references and duplicate fields in each object

DUPLICATE_OBJECTS

Default. Detects all duplicate objects, except Numbers and Strings

Note that all these policies, except for the default DUPLICATE_OBJECTS, are not recommended, and may result in incorrect mapping of your data.

Mapping Java primitives and Strings

The values of Java primitive types, of their Number and Boolean auto-boxing wrappers, and of Strings, are all mapped to the corresponding JSON literals, for example:

  mapper.toJson(2, int.class); // 2
  mapper.toJson(Integer.valueOf(2), Integer.class); // 2
  mapper.toJson(2, long.class); // 2
  mapper.toJson(2, Long.class); // 2
  mapper.toJson(2, double.class); // 2.0
  mapper.toJson(true, boolean.class); // true
  mapper.toJson("foo", String.class); // "foo"

Thanks to Java auto-boxing, the primitive types and their wrapper classes, like int and java.lang.Integer, are fully inter-operable in standard Java operations. Similarly, they are fully inter-operable in YaGson, for example:

  mapper.toJson(42, int.class); // 42
  mapper.toJson(42, Integer.class); // 42
  int resultInt1 = mapper.fromJson("42", int.class);
  int resultInt2 = mapper.fromJson("42", Integer.class);
  Integer resultInteger = mapper.fromJson("42", int.class);
  Integer resultInteger2 = mapper.fromJson("42", Integer.class);

Also, some sort of auto-conversion is supported. Despite the fact that Java literal 2 is of type int, you still can use it with the serialization type long.class. Similar conversion is allowed between other primitive numeric types too.

  mapper.toJson(2, int.class); // 2
  mapper.toJson(2L, int.class); // 2
  mapper.toJson(2, long.class); // 2
  mapper.toJson(2L, long.class); // 2
  mapper.toJson(2L, double.class); // 2.0
  mapper.toJson(2.1, long.class); // 2
Warning
As you can see in the last line of the previous sample, such auto-conversion can actually change the value due to the rounding applied.

As was stated above, no duplication checks are performed for Strings and values of primitive types, so your numbers are never represented as reference strings by YaGson. However, the use of @type\@val wrappers is still possible in some cases:

  mapper.toJson(2, Object.class); // {"@type":"java.lang.Integer","@val":2}
  mapper.toJson(2L, Object.class); // 2
  mapper.toJson(2.0F, Object.class); // {"@type":"java.lang.Float","@val":2.0}
  mapper.toJson(2.0, Object.class); // 2.0
  mapper.toJson(true, Object.class); // true
  mapper.toJson("foo", Object.class); // "foo"

Note that long, double, boolean and String Java types are considered to be the default de-serialization types for the corresponding types of JSON literals, so type wrappers are never used for them. For other Java types mapped to the same types of JSON literals, like int, type wrappers are used when the de-serialization type is not known or is not exact.

Mapping General Java Objects

The general Java objects are usually mapped to JSON objects on a field-by-field basis. Depending on the current policies and context, an object may also be mapped to a reference string, or have an extra @type\@val wrapper. See the above sections for details.

When mapped to a JSON Object, each non-static non-null field of the serialized Java object (declared either in the object class or one of its superclasses) is, in turn, serialized, using the formal type of the field as its serialization type.

As two or more fields of one object may have identical names if declared in different classes (e.g. in the actual class of the object and in one of its superclasses), YaGson is able to detect such ambiguity and mangle field names with ^num suffix, where num is 1 for the first superclass, 2 for its super-superclass etc. In the below example, two fields named str may be mapped to str (the field declared in the actual class of the serialized object) and str^1 (the field declared in its superclass):

class BaseStringHolder {
    String str = "baseStr";
}
class OverrideStringHolder extends BaseStringHolder {
    String str = "overrideStr";
}

...
  mapper.toJson(new OverrideStringHolder(), OverrideStringHolder.class);
  // {"str":"overrideStr","str^1":"baseStr"}

If the object class is a non-static inner, local, or anonymous class, then the links to its enclosing class are usually stored by Java in synthetic fields named like this$0. YaGson serializes these synthetic fields too, thus providing support for the serialization of such classes.

class Outer  {
    class Inner {
        String str = "foo";
    }

    Inner inner;
}

...
  Outer obj = new Outer();
  obj.inner = obj.new Inner();

  mapper.toJson(obj, Outer.class);
  // {"inner":{"str":"foo","this$0":"@root"}}

  mapper.toJson(obj.inner, Outer.Inner.class);
  // {"str":"foo","this$0":{"inner":"@root"}}

Finally, it shall be noted that the transient fields are also serialized. Although they are commonly thought of as "not for serialization", this is true only for the standard binary Java serialization, usually with a special in-class code which re-calculates the values of these transient fields. If such code is missing or incorrect, even the standard binary Java serialization would fail to preserve the correct behavior of these classes after de-serialization. Thus, a truly universal serializer designed to work with arbitrary classes MUST serialize the transient fields.

Mapping Arrays

Similarly to other JSON serializers, YaGson basically maps Java arrays directly to JSON Arrays on an element-by-element basis. But, depending on the current policies and context, a Java array may also be mapped to a reference string, or have an extra @type\@val wrapper, for example:

  Object[] obj = new Object[3];
  obj[0] = "foo";
  obj[1] = obj;
  obj[2] = "bar";

  mapper.toJson(obj, Object[].class);
  // ["foo","@root","bar"]

  mapper.toJson(obj, Object.class);
  // {"@type":"[Ljava.lang.Object;","@val":["foo","@root","bar"]}

Mapping Collections

Most other Java-to-JSON serializers map all Java Collections (including Lists, Sets etc.) to JSON Arrays containing all collection elements one by one, in order of the collection iteration: [element1, element2, …​]. This looks great, but there are some major drawbacks with such approach:

  • valuable behavior-related information, like Set's Comparator, is lost;

  • some collections, e.g. singleton or unmodifiable collections, cannot be de-serialized to their original classes, but only to some default collection implementations, like ArrayList or TreeSet, and only if the de-serialization types allow such replacements.

An alternative approach for YaGson would be to map all collections to JSON Objects field by field, just like described in Mapping General Java Objects. But, such representation is hardly readable for some collections; for instance, even an empty HashSet would be represented by a monstrous complicated long string instead of just [] as it used to be.

So, in order to keep the JSON representation as simple as possible, YaGson uses a combined approach:

  • if the collection is a simple one like ArrayList, HashSet etc., then it is mapped to a JSON Array, like by other JSON serializers;

  Collection<String> c = new TreeSet<>(asList("foo", "bar"));

  mapper.toJson(c, TreeSet.class);
  // ["bar","foo"]


  • if there is a non-default comparator or backing Map used in the collection, then that comparator or map are added as an extra element with a special syntax:

class LengthFirstStringComparator implements Comparator<String> {
    public int compare(String s1, String s2) {
        int cmp = s1.length() - s2.length();
        if (cmp == 0) {
            cmp = s1.compareTo(s2);
        }
        return cmp;
    }
}

...
  c = new TreeSet<>(new LengthFirstStringComparator());
  c.add("11");
  c.add("2");

  mapper.toJson(c, TreeSet.class);
  // ["@.m:",{"@.comparator":{"@type":"samples.LengthFirstStringComparator","@val":{}}},"2","11"]


In this sample, a TreeSet object has a non-default TreeMap backing map in its field m. So, the first two elements in the resulting JSON Array are the pair of the extra field declaration ("@.m:") and its JSON representation. Note that the backing map is serialized here as if it was empty. This is sufficient, as we only need its comparator declaration, not its entries.

  • if the collection delegates to another collection or map, or it is a special collection like singletonList, then it is represented as a JSON Object with all its fields serialized:

  mapper.toJson(Collections.singletonList("foo"), List.class);
  // {"@type":"java.util.Collections$SingletonList","@val":{"element":"foo"}}

  c = Collections.unmodifiableSet(new TreeSet<>(asList("foo", "bar")));
  mapper.toJson(c, Set.class);
  // {"@type":"java.util.Collections$UnmodifiableSet","@val":{"c":{"@type":"java.util.TreeSet","@val":["bar","foo"]}}}


In this sample, JSON representations without @type/@val wrappers are {"element":"foo"} and {"c":…​}, correspondingly.

  • finally, as with other objects, a collection may also be mapped to a reference string, or have an extra @type\@val wrapper if required by the serialization context.

Mapping Maps

Similarly to the case of Collections described above, YaGson uses a combined approach to represent Java Maps in JSON:

  • if the map is a simple one like HashMap and all its keys are simple too (i.e. the keys are representable as JSON strings or numeric or boolean literals, but not Objects or Arrays), then it is represented as a JSON Object like {key1:value1, key2:value2, …​};

  • if the map is a simple one like HashMap, but at least one of the keys is serialized as a JSON Object or Array, then it is represented as a JSON Array of key-value pairs like [[key1,value1], [key2,value2], …​];

  • in addition to the above cases, if there is a non-default comparator, then that comparator is added as an extra entry with a special syntax like {key1:value1, …​, "@.comparator": comparator} or [[key1,value1], …​, {"@.comparator": comparator}], depending on whether simple or complex keys are used.

  • if the map delegates to another map, or is a special one like singletonMap, then it is represented as a JSON Object with all its fields serialized;

  • finally, as with other objects, a map may also be mapped to a reference string, or have an extra @type\@val wrapper if required by the serialization context.

A few examples of such mappings may be found below:

  mapper.toJson(new HashMap<String, String>(), HashMap.class);
  // {}

  Map<String, String> sm = new TreeMap<>(new ShortestFirstStringComparator());
  sm.put("11", "foo");
  sm.put("2", "bar");
  mapper.toJson(sm, TreeMap.class);
  // {"2":"bar","11":"foo","@.comparator":{"@type":"samples.ShortestFirstStringComparator","@val":{}}}

  mapper.toJson(Collections.singletonMap("1", "foo"), Map.class);
  // {"@type":"java.util.Collections$SingletonMap","@val":{"k":"1","v":"foo"}}

  mapper.toJson(Collections.unmodifiableMap(new TreeMap<>()), Map.class);
  // {"@type":"java.util.Collections$UnmodifiableMap","@val":{"m":{"@type":"java.util.TreeMap","@val":{}}}}

  Map<Person, String> pm = new TreeMap<>();
  pm.put(new Person("John", "Doe"), "M");
  pm.put(new Person("Jane", "Doe"), "F");
  mapper.toJson(pm, new TypeToken<TreeMap<Person,String>>(){}.getType());
  // [[{"name":"Jane","family":"Doe"},"F"],[{"name":"John","family":"Doe"},"M"]]

Mapping Special Objects

Some Java classes have special support in YaGson, either for the simplicity of their JSON representation, or because the general objects mapping approach does not work well for them. For example:

  • Threads and ThreadGroups are mapped to Strings which represent the full paths from the root ThreadGroup to the serialized thread or group, e.g. "system.MyWorkerThread-1"

  • for a ThreadLocal, the saved data includes all fields of the actual ThreadLocal's class (so the initialValue() behavior is preserved), and, additionally, the local value assigned to the current Thread, if any;

  • ClassLoaders are very special objects in Java, which cannot be correctly serialized. So, they are always mapped to nulls;

  • Class objects are mapped to the full class name, like "java.lang.String";

  • URL, URI, UUID, StringBuilder, StringBuffer, Locale, InetAddress, Date, Time, Timestamp, SqlDate objects are all mapped to JSON Strings;

  • Instances of all Reference classes, like WeakReference, SoftReference and PhantomReference, are serialized without their current values, as if they were already GC’ed

Mapping Java 8 Lambda Expresssions

Usually, there is no need to serialize Lambda Expressions, as they do not represent any data. However, it is quite possible that an object being serialized contains some lamdas, e.g. a serialized Map object may contain custom Comparator implemented with the use of Lambda Expressions. That’s why support of Lambda Expressions is still required.

By default, neither Lambda Expressions or Method References are Serializable in Java 8. Even for simplest references like ClassName::methodName, there is no information available at run time which would allow to map the resulting Lambda Expression Object to the actual method.

Such Lambda Expressions are non-Serializable and are skipped by YaGson; in other words, they are mapped to nulls.

  // all lambda expressions below are non-serializable

  Runnable nsl1 = () -> System.out.println("Hello!");
  Supplier nsl2 = () -> "foo";
  Predicate<Integer> nsl3 = i -> i > 0;

  mapper.toJson(nsl1);
  // "null"
  mapper.toJson(System.out::println);
  // "null"

In order to make a Lambda Expression or a Method Reference Serializable, one need to cast it to an intersection of a functional interface type and the Serializable type, e.g.

  // all lambda expressions below are serializable
  Runnable sl1 = (Runnable & Serializable)() -> System.out.println("Hello!");
  Supplier sl2 = (Supplier & Serializable) () -> "foo";
  Predicate<Integer> sl3 = (Predicate<Integer> & Serializable) (i) -> i > 0;

  String json = mapper.toJson(sl1);
  Runnable result = mapper.fromJson(json, Runnable.class);

Such serializable lambdas are correctly serialized as an instance of java.lang.invoke.SerializedLambda class and de-serialized back to a Lambda Expression object of the specified functional interface type.

Java 9 Compatibility

Despite the fact that YaGson internally use Java Reflection API for its operation, it does it in a way which causes neither "illegal reflection operation" warnings, nor IllegalAccessException even if the --illegal-access=deny JVM argument is used.

So, it can be safely used to serialize and deserialize objects from different modules.

Using YaGson with REST and Micro-services

Nowadays, one of the popular approaches to Java client-server communication is using RESTful web services that sends and accepts Java entities using their JSON representation.

If there is an option to customize the JSON serializer used on both client and server sides, YaGson is a smart choice, as it supports much wider range of Java entities than other popular JSON serializers.

The section below describes how to integrate YaGson with RESTful web services based on Spring MVC (version 4.3 or later is required).

Integration with Spring MVC

When Spring MVC is used as the web framework, RESTful web services are usually implemented using @Controllers, and consumed using @RestTemplates.

In both cases, the serializer to use is determined by a configured set of HttpMessageConverters. Currently, the only provided default JSON converters are for Gson and Jackson/Jackson2, so you need to add and configure YaGsonHttpMessageConverter manually.

At first, in Maven projects, add the following dependency:

  <dependency>
    <groupId>com.gilecode.yagson</groupId>
    <artifactId>yagson-spring4-converters</artifactId>
    <version>0.2</version>
  </dependency>

Then, on the server side, create and add YaGsonHttpMessageConverter bean to the list of available converters:

@Configuration
public class RestConfig extends WebMvcConfigurationSupport {

    @Bean
    public YaGsonHttpMessageConverter yagsonMessageConverter() {
        // if no parameters passed, the created converter works only with the 'application/yagson' media type
        return new YaGsonHttpMessageConverter();
    }


    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.addDefaultHttpMessageConverters(converters);
        converters.add(yagsonMessageConverter());
    }
}

This way, all incoming entities will be de-serialized by YaGson if the HTTP request header Content-Type has the value application/yagson. Similarly, outgoing entities will be serialized by YaGson if the HTTP request header Accept has the value application/yagson.

@RestController
@RequestMapping("/yagson")
public class PersonYaGsonController {

    @Autowired
    private PersonDAO personDao;

    @RequestMapping(method = RequestMethod.POST, value = "/person")
    public void handleAddPerson(@RequestBody Person p) {
        personDao.addPerson(p);
    }

    @RequestMapping("/persons")
    public Set<Person> handleGetPersons() {
        return personDao.getPersons();
    }
}
Note
Although it is possible to totally replace all default converters with the YaGson-based converter (which shall be created as new YaGsonHttpMessageConverter(true) in this case), such configuration is not recommended, as it will affect all Spring MVC controllers in your application.

On the client side, pass the YaGsonHttpMessageConverter instance to the RestTemplate constructor to use YaGson for the serialization. It will ensure that the correct media type application/yagson is set to the HTTP headers Content-Type and Accept where applicable. Then, use RestTemplate's exchange(…​) method for all requests:

  RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new YaGsonHttpMessageConverter()));

  RequestEntity<Person> postRequestEntity = new RequestEntity<>(
        new PersonEx("Mr.", "Sample", "Person2"),
        HttpMethod.POST,
        URI.create("http://localhost:8080/yagson/person"),
        Person.class // NOTE: the type accepted on the server side is Person, not PersonEx!
        );
  restTemplate.exchange(postRequestEntity, Void.class);

  RequestEntity getRequestEntity = new RequestEntity<>(
       null, // no request body
       HttpMethod.GET,
       URI.create("http://localhost:8080/yagson/persons"));
  ResponseEntity<Set<Person>> result = restTemplate.exchange(
      getRequestEntity,
      new ParameterizedTypeReference<Set<Person>>() {});

  Set<Person> persons = result.getBody();
   ...
Warning
As of Spring 4.3, there is no way to pass the expected de-serialization type to any RestTemplate's method other than exchange(…​). So, the use of methods like put(…​), postForEntity(…​), getForEntity(…​) etc. is not recommended.

The runnable sample illustrating the use of YaGson in Spring MVC server and client applications is available at yagson-spring-rest-sample. At first, run ServerApp, then ClientApp, and see the results.

Limiting the length of output JSON string

When serializing the really complex objects, like the whole Tomcat’s context, the resulting JSON string may be very long, like billions of characters, and consume gigabytes of RAM. In most cases, such enormous resulting strings are undesirable, and it would be better to rapidly obtain a truncated JSON string instead.

Luckily, it is now possible with YaGson, see methods toJson(Object src, Type deserializationType, long charsLimit) and toJson(Object src, long charsLimit). Both these methods limit the output length, and throw StringOutputLimitExceededException if the limit is exceeded. The exception instance may be used to obtain the truncated result, if necessary:

  try {
    return mapper.toJson(obj, Object.class, cLimit);
  } catch (StringOutputLimitExceededException e) {
    // limit exceeded
    return e.getTruncatedResult() + "...";
  }