I am using Jackson v2.8.2 to serialise JSON to a file.
I have created a custom serializer and implemented the serialize method to customise the JSON output as required.
I am invoking the serializer as follows:
// myClass is the object I want to serialize
SimpleModule module = new SimpleModule();
module.addSerializer(MyClass.class, new MySerializer());
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(module);
try
{
mapper.writeValue(new File("json.txt"), myClass);
}
catch (JsonProcessingException e)
{
...
}
The JSON file is created and the content looks good.
The file is formatted according to the DefaultPrettyPrinter but I want to use my own custom PrettyPrinter, which I have already implemented.
How do I do that?
I've tried the following:
MyPrettyPrinter myPrettyPrinter = new MyPrettyPrinter();
mapper.writer(myPrettyPrinter);
mapper.writeValue(new File("json.txt"), myClass);
but that isn't invoking my custom printer.
Sometimes, depending on what you want to achieve, you could use the DefaultPrettyPrinter and just customize the Indenter, as following:
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
Indenter indenter = new CustomSpaceIndenter();
printer.indentObjectsWith(indenter); // Indent JSON objects
printer.indentArraysWith(indenter); // Indent JSON arrays
There's a related question about it: Serialize JsonNode to a very specific JSON format in Jackson
The reason for this is that the invocation of writer returns a new instance of the ObjectWriter. In fact, ObjectMapper has a lot of factory methods that construct new objects for you to work with.
The sourcecode from ObjectMapper:
/**
* Factory method for constructing {#link ObjectWriter} that will
* serialize objects using specified pretty printer for indentation
* (or if null, no pretty printer)
*/
public ObjectWriter writer(PrettyPrinter pp) {
if (pp == null) { // need to use a marker to indicate explicit disabling of pp
pp = ObjectWriter.NULL_PRETTY_PRINTER;
}
return _newWriter(getSerializationConfig(), /*root type*/ null, pp);
}
So for you that means, that you should change your code to:
MyPrettyPrinter myPrettyPrinter = new MyPrettyPrinter();
ObjectWriter myWriter = mapper.writer(myPrettyPrinter);
myWriter.writeValue(new File("json.txt"), myClass);
Note the assignment to myWriter so that you are using the correct writer when calling writeValue
Here is an example using the ObjectMapper and the default pretty printer:
public class OMTest {
public static void main(String[] args) throws IOException {
// test string
String json = " {\"a\" : \"b\", \"c\" : \"d\" } ";
// mapper
ObjectMapper mapper = new ObjectMapper();
// json tree
JsonNode tree = mapper.readTree(json);
// the objectWriter assigned with a pretty printer
ObjectWriter myWriter = mapper.writer(new DefaultPrettyPrinter());
// print without pretty printer (using mapper)
System.out.println(mapper.writeValueAsString(tree));
System.out.println();
// print with writer (using the pretty printer)
System.out.println(myWriter.writeValueAsString(tree));
}
}
This prints:
{"a":"b","c":"d"}
{
"a" : "b",
"c" : "d"
}
Where the first line uses the mapper, while the second print uses the writer.
Cheers,
Artur
Related
The com.fasterxml.jackson.databind.ObjectMapper JavaDocs say:
Mapper instances are fully thread-safe provided that ALL configuration
of the instance occurs before ANY read or write calls. If
configuration of a mapper instance is modified after first usage,
changes may or may not take effect, and configuration calls themselves
may fail. If you need to use different configuration, you have two
main possibilities:
Construct and use ObjectReader for reading, ObjectWriter for writing.
Both types are fully immutable and you can freely create new instances
with different configuration using either factory methods of
ObjectMapper, or readers/writers themselves. Construction of new
ObjectReaders and ObjectWriters is a very light-weight operation so it
is usually appropriate to create these on per-call basis, as needed,
for configuring things like optional indentation of JSON.
Is it OK to make this call every time I need a new ObjectWriter?
jsonString = new MyObjectWriter().objectWriter().writeValueAsString(myPojo);
Where MyObjectWriter looks like this:
public class MyObjectWriter {
public ObjectWriter objectWriter()
{
return new ObjectMapper()
.writer()
.with(SerializationFeature.INDENT_OUTPUT)
.with(JsonGenerator.Feature.IGNORE_UNKNOWN);
}
}
Should I hang on to a copy of the ObjectMapper? The ObjectWriter?
Like documentation says this is really cheap operation and you can do that "on per call basis". Let's see what we have behind each method.
ObjectMapper.writer - creates new ObjectWriter using SerializationConfig from ObjectMapper.
ObjectWriter.with - creates new ObjectWriter which is based on caller instance plus new feature which must be enabled. In case given feature is already enabled the same instance is returned. If feature changes configuration - new ObjectWriter is created and returned.
Let's see example application which shows given scenario:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.Collections;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer0 = mapper.writer();
ObjectWriter writer1 = writer0.with(SerializationFeature.INDENT_OUTPUT);
ObjectWriter writer2 = writer1.with(SerializationFeature.INDENT_OUTPUT);
ObjectWriter writer3 = writer2.with(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS);
Map<String, Long> map = Collections.singletonMap("key", 123L);
System.out.println(writer0 + " = " + writer0.writeValueAsString(map));
System.out.println(writer1 + " = " + writer1.writeValueAsString(map));
System.out.println(writer2 + " = " + writer2.writeValueAsString(map));
System.out.println(writer3 + " = " + writer3.writeValueAsString(map));
ObjectMapper mapper1 = new ObjectMapper();
mapper1.enable(SerializationFeature.INDENT_OUTPUT);
mapper1.enable(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS);
ObjectWriter writer4 = mapper1.writer();
System.out.println(writer4 + " = " + writer4.writeValueAsString(map));
}
}
Above app prints:
com.fasterxml.jackson.databind.ObjectWriter#2ed94a8b = {"key":123}
com.fasterxml.jackson.databind.ObjectWriter#2a5ca609 = {
"key" : 123
}
com.fasterxml.jackson.databind.ObjectWriter#2a5ca609 = {
"key" : 123
}
com.fasterxml.jackson.databind.ObjectWriter#20e2cbe0 = {
"key" : "123"
}
com.fasterxml.jackson.databind.ObjectWriter#68be2bc2 = {
"key" : "123"
}
Notice, that second (writer1) and third (writer2) instances (com.fasterxml.jackson.databind.ObjectWriter#2a5ca609) are the same. They also generate the same JSON payload.
So, using first instance of ObjectMapper we created and configured ObjectWriter. But mainly only the last one is used. All in between are already gone and wait for collecting by GC. There is no point to do that. It is better to create ObjectMapper instance, configure it and create ObjectWriter already configured by calling writer() method. You can create Factory-like class for configured instances of ObjectMapper and you can use these instances to generate ObjectWriter-s.
I have the following code to convert an object to Json:
public static Function<Object, Object> WRITE_JSON = (Object val) -> {
try {
return new ObjectMapper().writeValueAsString(val);
} catch (IOException e) {
// log exception
return "";
}
}
This works fine for most cases, but f.e I have an Avro class named AvroData, and a class that saves it:
class SomeData {
private AvroData avroData;
// more fields, getter/setter boilerplate, etc...
}
When I try to serialise the object to Json, this fails when trying to serialize the Avro field.
In reality, I have a bit more data, like Sets and Maps that contain Avro record values, but I think the point stands.
How do you manage to serialise a avro to json, but specifically when it's part of a Non-avro object?
To convert your Object val in JSON with Jackson:
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(val);
If I use a YAMLFactory instead of default one (JSON) for ObjectMapper and configure a custom indentation, the indentation is not taken into account
If I don't use YAML output, it works for the JSON output.
Any idea ?
DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(" ", DefaultIndenter.SYS_LF);
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
printer.indentObjectsWith(indenter);
printer.indentArraysWith(indenter);
ObjectMapper objectMapper = new ObjectMapper( new YAMLFactory() );
objectMapper.setDefaultPrettyPrinter(printer);
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
String string = objectMapper.writeValueAsString(myObject);
According to http://fasterxml.github.io/jackson-dataformat-csv/javadoc/2.0.0/com/fasterxml/jackson/dataformat/csv/CsvParser.Feature.html, WRAP_AS_ARRAY is:
Feature that determines how stream of records (usually CSV lines, but sometimes multiple lines when linefeeds are included in quoted values) is exposed: either as a sequence of Objects (false), or as an array of Objects (true).
What is the difference between a "sequence of Objects" and an "array of Objects"? The description seems the same to me.
Parsing to a sequence of objects: you call readValues() and get a MappingIterator, which gives you the objects one-by-one. Equivalent to input containing multiple JSON objects, one after the other.
Parsing to an array of objects: you call readValue() and get a List of the objects. Equivalent to input containing a JSON array.
Examples:
#Test
public void parses_csv_to_object_list() throws Exception {
String csv = "id,name\n1,Red\n2,Green\n3,Blue";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(ColourData.class).withHeader();
ObjectReader reader = mapper.readerFor(ColourData.class).with(schema);
try (MappingIterator<ColourData> iter = reader.readValues(csv)) {
assertThat(iter.readAll(),
contains(new ColourData(1, "Red"), new ColourData(2, "Green"), new ColourData(3, "Blue")));
}
}
#Test
public void parses_csv_to_object_list_in_one_read() throws Exception {
String csv = "id,name\n1,Red\n2,Green\n3,Blue";
CsvMapper mapper = new CsvMapper().enable(CsvParser.Feature.WRAP_AS_ARRAY);
CsvSchema schema = mapper.schemaFor(ColourData.class).withHeader();
ObjectReader reader = mapper.readerFor(new TypeReference<List<ColourData>>() {
}).with(schema);
assertThat(reader.readValue(csv),
contains(new ColourData(1, "Red"), new ColourData(2, "Green"), new ColourData(3, "Blue")));
}
I am working with Jackson 2 and CXF.
I have done lots of research to find a clean and safe way to get a writer object from the shared object mapper that is given to CXF for un/marshaling JSON. I cannot just use annotation or set the mapper object to ignore null fields when serializing due to some business logic.
The code below seem to be very correct, but the output JSON still include null fields. Please help !!
ObjectWriter writer = this.jacksonMapper.writer().without( SerializationFeature.WRITE_NULL_MAP_VALUES ) ;
if( writer.isEnabled( SerializationFeature.WRITE_NULL_MAP_VALUES ) ) {
System.out.println("Oppa gangname style");
}
String json = null;
try {
json = writer.writeValueAsString( myObject );
System.out.println ( json ) ;
} catch (JsonProcessingException e) {
throw new RuntimeException() ;
}
The if case verify that I have successful disable SerializationFeature.WRITE_NULL_MAP_VALUES.
However, the result is still include null fields.
I'm using an older Jackson version but this works for me:
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
mapper.setSerializationInclusion(Inclusion.NON_NULL);
return mapper.writeValueAsString(input);
The docs say SerializationFeature.WRITE_NULL_MAP_VALUES only applies to generating JSON strings from Map objects.