JsonSerializer/JsonDeserializer (example)

Hello guys,
if you are looking for a solution to convert your configuration classes into json files, here is a quick ready-to-use example, among the many you could find on the web. In particular, I focused on the serialization and deserialization of the ColorRGBA, Vector3f and Quaternion jme classes.

The libraries I used are:

  • jackson-annotation-2.9.0.jar
  • jackson-core-2.9.8.jar
  • jackson-databind-2.9.8.jar

To simplify the configuration of the ObjectMapper, I centralized all the serializers into a single reusable JmeJsonModule class which extends SimpleModule. This way I have a complete and clear view of how information is encoded / decoded in a single file without having to create a class for each serializer (see also the method: setupModule(SetupContext context) )

Hope this was helpful, let me know if you have used a better solution.

import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

public class JmeJsonModule extends SimpleModule {
	
	private static final long serialVersionUID = 1L;

	class QuaternionDeserializer extends JsonDeserializer<Quaternion> {
		@Override
		public Quaternion deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
			// TODO Auto-generated method stub
			float[] values = parser.readValueAs(float[].class);
			return new Quaternion(values[0], values[1], values[2], values[3]);
		}
	}

	class QuaternionSerializer extends JsonSerializer<Quaternion> {
		@Override
		public void serialize(Quaternion quat, JsonGenerator gen, SerializerProvider provider) throws IOException {
			// TODO Auto-generated method stub
			gen.writeStartArray(4);
			gen.writeNumber(quat.getX());
			gen.writeNumber(quat.getY());
			gen.writeNumber(quat.getZ());
			gen.writeNumber(quat.getW());
			gen.writeEndArray();
		}
	}

	class Vector3Deserializer extends JsonDeserializer<Vector3f> {
		@Override
		public Vector3f deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
			// TODO Auto-generated method stub
			float[] values = parser.readValueAs(float[].class);
			return new Vector3f(values[0], values[1], values[2]);
		}
	}

	class Vector3Serializer extends JsonSerializer<Vector3f> {
		@Override
		public void serialize(Vector3f vec, JsonGenerator gen, SerializerProvider provider) throws IOException {
			// TODO Auto-generated method stub
			gen.writeStartArray(3);
			gen.writeNumber(vec.getX());
			gen.writeNumber(vec.getY());
			gen.writeNumber(vec.getZ());
			gen.writeEndArray();
		}
	}

	class ColorDeserializer extends JsonDeserializer<ColorRGBA> {
		@Override
		public ColorRGBA deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
			// TODO Auto-generated method stub
			float[] values = parser.readValueAs(float[].class);
			return new ColorRGBA(values[0], values[1], values[2], values[3]);
		}
	}

	class ColorSerializer extends JsonSerializer<ColorRGBA> {
		@Override
		public void serialize(ColorRGBA color, JsonGenerator gen, SerializerProvider provider) throws IOException {
			// TODO Auto-generated method stub
			gen.writeStartArray(4);
			gen.writeNumber(color.getRed());
			gen.writeNumber(color.getGreen());
			gen.writeNumber(color.getBlue());
			gen.writeNumber(color.getAlpha());
			gen.writeEndArray();
		}
	}

	@Override
	public void setupModule(SetupContext context) {
		super.setupModule(context);

		SimpleSerializers ser = new SimpleSerializers();
		ser.addSerializer(Quaternion.class, new QuaternionSerializer());
		ser.addSerializer(Vector3f.class, new Vector3Serializer());
		ser.addSerializer(ColorRGBA.class, new ColorSerializer());
		context.addSerializers(ser);
		
		SimpleDeserializers des = new SimpleDeserializers();
		des.addDeserializer(Quaternion.class, new QuaternionDeserializer());
		des.addDeserializer(Vector3f.class, new Vector3Deserializer());
		des.addDeserializer(ColorRGBA.class, new ColorDeserializer());
		context.addDeserializers(des);
	}
}

Here my example bean class filled with random values:

	public class MyBean {
		
		public HumanBodyBones humanBone;
		public String boneName;
		public Vector3f position;
		public Quaternion rotation;
		public ColorRGBA color;
		
		public MyBean() {
			HumanBodyBones[] values = HumanBodyBones.values();
			humanBone 	= values[FastMath.nextRandomInt(0, values.length - 1)];
			boneName 	= "Armature_mixamorig:" + humanBone.getBoneName();
			position 	= getRandomPosition();
			rotation	= Quaternion.IDENTITY;
			color 		= ColorRGBA.randomColor();
		}
		
		private Vector3f getRandomPosition() {
			float x = FastMath.nextRandomFloat();
			float y = FastMath.nextRandomFloat();
			float z = FastMath.nextRandomFloat();
			return new Vector3f(x, y, z);
		}
	    
		@Override
	    public String toString() {
	        return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
	    }
	}

And here the Main class to lunch the test:

public class Test_Json {

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		ObjectMapper mapper = new ObjectMapper();
		mapper.registerModule(new JmeJsonModule());
		mapper.enable(SerializationFeature.INDENT_OUTPUT);
        
        List<MyBean> lstInput = Arrays.asList(new MyBean(), new MyBean(), new MyBean());
        String json = mapper.writeValueAsString(lstInput);
        System.out.println(json);
        
        // Unmarshall to Collection
//        List<MyBean> lstOutput = mapper.readValue(json, new TypeReference<List<MyBean>>(){});
        
        // Unmarshall to Array
        MyBean[] array = mapper.readValue(json, MyBean[].class);
		System.out.println(Arrays.toString(array));
	}

And here the output:

[ {
  "humanBone" : "LEFT_SHOULDER",
  "boneName" : "Armature_mixamorig:LeftShoulder",
  "position" : [ 0.83388364, 0.39985967, 0.01943177 ],
  "rotation" : [ 0.0, 0.0, 0.0, 1.0 ],
  "color" : [ 0.42542416, 0.20122874, 0.7825847, 1.0 ]
}, {
  "humanBone" : "LEFT_SHOULDER",
  "boneName" : "Armature_mixamorig:LeftShoulder",
  "position" : [ 0.31399703, 0.5166057, 0.88700897 ],
  "rotation" : [ 0.0, 0.0, 0.0, 1.0 ],
  "color" : [ 0.8347665, 0.61224395, 0.30979508, 1.0 ]
}, {
  "humanBone" : "SPINE_1",
  "boneName" : "Armature_mixamorig:Spine1",
  "position" : [ 0.35129148, 0.87165403, 0.20744377 ],
  "rotation" : [ 0.0, 0.0, 0.0, 1.0 ],
  "color" : [ 0.644656, 0.9456146, 0.010902762, 1.0 ]
} ]

[Test_Json.MyBean[
humanBone=LEFT_SHOULDER,
boneName=Armature_mixamorig:LeftShoulder,
color=Color[0.42542416, 0.20122874, 0.7825847, 1.0],
position=(0.83388364, 0.39985967, 0.01943177),
rotation=(0.0, 0.0, 0.0, 1.0)],

Test_Json.MyBean[
humanBone=LEFT_SHOULDER,
boneName=Armature_mixamorig:LeftShoulder,
color=Color[0.8347665, 0.61224395, 0.30979508, 1.0],
position=(0.31399703, 0.5166057, 0.88700897),
rotation=(0.0, 0.0, 0.0, 1.0)],

Test_Json.MyBean[
humanBone=SPINE_1,
boneName=Armature_mixamorig:Spine1,
color=Color[0.644656, 0.9456146, 0.010902762, 1.0],
position=(0.35129148, 0.87165403, 0.20744377),
rotation=(0.0, 0.0, 0.0, 1.0)]]

1 Like

It seems a little strange that you needed custom serializers for Vector3f and ColorRGBA. Didn’t those work automatically? I know public fields are usually no problem for jackson or GSON.

If you don’t want to use a custom serializer, the code changes this way.

public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		ObjectMapper mapper = new ObjectMapper();
		mapper.registerModule(new SimpleModule ());
		mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

In this way the structure of the json completely changes with the addition of the fields unitVector and identity which at the moment I don’t need.

"position" : {
    "x" : 0.96857226,
    "y" : 0.11478108,
    "z" : 0.22742343,
    "unitVector" : true
  },
  "rotation" : {
    "x" : 0.0,
    "y" : 0.0,
    "z" : 0.0,
    "w" : 1.0,
    "identity" : true
  }

Also you need to add the this option otherwise deserialization fails:

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "unitVector" (class com.jme3.math.Vector3f), not marked as ignorable (3 known properties: "x", "y" , "z"])

Configuring the x, y, z values ​​in this structure is more inconvenient for me. It is generally easier to copy fields from the constructor of the new Vector3f object (1.0f, 2.0f, 3.0f) and paste them in the json file.

In this scenario it seems that the default serialization of the ColorRGBA class does not work. For this I wrote the 3 custom serializers / deserializers.

1 Like

Data gathering for ML DL4J :grinning: you have invented something like DataVec Nice work , so you can feed this data into an ML Model or use them for game statistics

1 Like