I am using Jackson and would like to pretty-print JSON such that each element in arrays goes to each line, like:
{
"foo" : "bar",
"blah" : [
1,
2,
3
]
}
Setting SerializationFeature.INDENT_OUTPUT true only inserts newline characters for object fields, not array elements, printing the object in this way instead:
{
"foo" : "bar",
"blah" : [1, 2, 3]
}
Does anyone know how to achieve this? Thanks!
If you don't want to extend DefaultPrettyPrinter you can also just set the indentArraysWith property externally:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
String json = objectMapper.writer(prettyPrinter).writeValueAsString(object);
Thanks to the helpful hints, I was able to configure my ObjectMapper with desired indentation as follows:
private static class PrettyPrinter extends DefaultPrettyPrinter {
public static final PrettyPrinter instance = new PrettyPrinter();
public PrettyPrinter() {
_arrayIndenter = Lf2SpacesIndenter.instance;
}
}
private static class Factory extends JsonFactory {
#Override
protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException {
return super._createGenerator(out, ctxt).setPrettyPrinter(PrettyPrinter.instance);
}
}
{
ObjectMapper mapper = new ObjectMapper(new Factory());
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
}
You could extend the DefaultPrettyPrinter and override the methods beforeArrayValues(…) and writeArrayValueSeparator(…) to archieve the desired behaviour. Afterwards you have to add your new Implementation to your JsonGenerator via setPrettyPrinter(…).
Mapper can be configured (jackson-2.6) with:
ObjectMapper mapper = ...
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
mapper.setDefaultPrettyPrinter(prettyPrinter);
//use mapper
The answer thankfully provided by OP shows a way for obtain a single-array-element-per-line formatted JSON String from writeValueAsString. Based on it here a solution to write the same formatted JSON directly to a file with writeValue with less code:
private static class PrettyPrinter extends DefaultPrettyPrinter {
public static final PrettyPrinter instance = new PrettyPrinter();
public PrettyPrinter() {
_arrayIndenter = Lf2SpacesIndenter.instance;
}
}
{
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer(PrettyPrinter.instance);
writer.writeValue(destFile, objectToSerialize);
}
try out JSON Generator...
API Reference
good example
Related
This question already has answers here:
Excluding NullNode when serializing a Jackson JSON tree model
(3 answers)
Closed 11 months ago.
ObjectMapper still includes null values. I tried a lot of solutions found here, but nothing works. I cannot use json annotation, so my only solution is predefined setting of mapper, but this was not reflected. I thought this was due to caching of objectMapper. But my only modifies of mapper are made in Constructor. So caching would not be a problem
Dependencies:
Log4J2: 2.17.1
Fasterxml Jackson annotation: 2.13.2
Fasterxml Jackson databind: 2.13.2
Wildfly: 20.0.1
OpenJDK: 11.0.14.1
I have an objectMapper defined as global value which is instantiated in constructor. Then I have one method for building a JSON which accepts key and value. As value can by anything.
private final ObjectMapper jsonMapper;
public SomeConstructor() {
this.jsonMapper = new ObjectMapper();
this.jsonMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
this.jsonMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
}
#Override
public void setJsonVar(String jsonVar, String jsonKey, Object values) {
// loads ObjectNode from memory if exists
ObjectNode jsonNode = getJsonVar(jsonVar);
// lazy init if ObjectNode not exists
if (jsonNode == null) {
jsonNode = jsonMapper.createObjectNode();
}
// add object
jsonNode.putPOJO(jsonKey, values);
}
Usage:
setJsonVar("var-A", "key-A", 1);
setJsonVar("var-A", "key-B", null);
print("var-a");
Expectation:
I want to avoid null values in JSON.
Expected: var-A: { "key-A":1 }
Got: var-A: { "key-A":1, "key-B":null }
Why does this happen and what can I do to work around this?
This option is applicable when serializing objects, custom objects or a Map for example, but not when working with json tree. Consider this Foo class:
public class Foo {
private String id;
private String name;
//getters and setters
}
The option to exclude nulls will work as expected with it.
Main method to illustrate it:
public class Main {
public static void main(String[] args) throws Exception {
serializeNulls();
System.out.println();
doNotSerializeNulls();
}
private static void serializeNulls() throws Exception {
ObjectMapper jsonMapper = new ObjectMapper();
Foo foo = new Foo();
foo.setId("id");
System.out.println("Serialize nulls");
System.out.println(jsonMapper.writeValueAsString(foo));
Map<String, Object> map = new LinkedHashMap<>();
map.put("key1", "val1");
map.put("key2", null);
System.out.println(jsonMapper.writeValueAsString(map));
}
private static void doNotSerializeNulls() throws Exception {
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jsonMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
Foo foo = new Foo();
foo.setId("id");
System.out.println("Do not serialize nulls");
System.out.println(jsonMapper.writeValueAsString(foo));
Map<String, Object> map = new LinkedHashMap<>();
map.put("key1", "val1");
map.put("key2", null);
System.out.println(jsonMapper.writeValueAsString(map));
}
}
I have a generic class like this:
public class Pojo<T> {
#JsonProperty("value")
public T[] values;
};
The T can either hold a String, a LocalDateTime or an Integer. The differentiation between String and Integer seems to work fine, most likely because those types are represented differently in the serialized JSON file. However, when I have a datetime in my JSON object (see example), it is parsed as a string anyway.
The actual type in use for the value field is determined by the input. While I do have that knowledge in the following minimal example, this is not true for all uses of this code in my program. I can only rely on the pattern of the value of field value.
public class Main {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
String json = "{\"value\": \"2022-02-22T12:00:00\"}";
Pojo<?> deserialized = mapper.readValue(json, Pojo.class);
assert deserialized.value[0] instanceof LocalDateTime;
}
I haven't had any success tinkering with JsonTypeInfo yet. Is there a way to parse the value field as an array of LocalDateTime objects if all values for this field match the pattern yyyy-MM-dd'T'hh:mm:ss?
Here is your problem - Pojo<?> deserialized = mapper.readValue(json, Pojo.class).
ObjectMapper does not know what type to parse T to. To tell it the type you need to use either readValue overload with TypeReference, or the overload with JavaType. I find JavaType easier to read, so here is an example with it:
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
String json = "{\"value\": \"2022-02-22T12:00:00\"}";
JavaType javaType = TypeFactory.defaultInstance().constructParametricType(Pojo.class, LocalDateTime.class);
Pojo<LocalDateTime> deserialized = mapper.readValue(json, javaType);
System.out.println(deserialized.values[0].toLocalDate());
System.out.println(deserialized.values[0].toLocalTime());
}
}
When constructing the parametric JavaType the first argument is the class itself, next are concrete generic types.
Edit: Having in mind the new info, the best i can come up with is custom deserializer, which resolves the type at runtime. Something like this:
public class UnknownTypeDeserializer<T> extends StdDeserializer<Pojo<T>> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
public UnknownTypeDeserializer() {
super((Class<?>) null);
}
#Override
public Pojo<T> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = parser.getCodec().readTree(parser);
String value = node.get("value").asText();
Pojo<T> pojo = new Pojo<>();
T[] arr;
try {
arr = (T[]) new LocalDateTime[]{LocalDateTime.parse(value, this.formatter)};
} catch (Exception exc) {
try {
arr = (T[]) new Integer[]{Integer.parseInt(value)};
} catch (NumberFormatException numberFormatException) {
arr = (T[]) new String[]{value};
}
}
pojo.values = arr;
return pojo;
}
}
The idea is try to parse to LocalDateTime, if not possible, try to parse to int, if not possible again leave as String. That's quite the ugly way to do it, but i am trying just to illustrate the idea. Instead of catching exceptions, it might be better to examine the format, using regex for example, and according to which regex matches, parse to correct type. I tested it with string and int, and it actually works.
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Pojo.class, new UnknownTypeDeserializer<>());
mapper.registerModule(simpleModule);
String json1 = "{\"value\": \"2022-02-22T12:00:00\"}";
Pojo<?> deserialized1 = mapper.readValue(json1, Pojo.class);
System.out.println("is date - " + (deserialized1.values[0] instanceof LocalDateTime));
String json2 = "{\"value\": \"bla bla bla\"}";
Pojo<?> deserialized2 = mapper.readValue(json2, Pojo.class);
System.out.println("is string - " + (deserialized2.values[0] instanceof String));
String json3 = "{\"value\": 41}";
Pojo<?> deserialized3 = mapper.readValue(json3, Pojo.class);
System.out.println("is int - " + (deserialized3.values[0] instanceof Integer));
}
}
I have a class (Jackson annotations/getters/setters/etc are omitted):
public class Sample {
public String name;
public Integer value;
}
I have an instance, e.g.:
Sample sample = new Sample("one", null),
and i have a json string:
{"name" = "two", "value" = 3}
And i update the object with json:
ObjectMapper mapper = new ObjectMapper();
mapper.readerForUpdating(sample).readValue(json);
After updating my object looks like this:
[Sample: name = "two", value = 3]
But i need do not overwrite not null fields, as the name is, so my object after updating would looks like this:
[Sample: name = "one", value = 3]
Unfortunally, i can't edit my class and Jackson annotations, so i need to change somehow a config of my mapper. Is threre a way to do it?
The idea behind the readerForUpdating method is not to create a new instance of the object,just to replace the values of the passed object into the object for update.
I had the same problem ,wanted to replace ONLY the values that are not null,but to do that I needed to isolate the ObjectMapper and configure it to not transfer null values ,which combined with the readerForUpdating method does what we want:
public static void updateModels(Object original,Object data) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
try {
objectMapper.readerForUpdating(original).readValue(objectMapper.writeValueAsBytes(data));
} catch (IOException e) {
e.printStackTrace();
}
}
I'm trying to parse json strings inside a json string into an Object using Jackson ObjectMapper. But I can't find a clean way to do this.
I have a SQL table storing Lists as json strings like "[1,2,3]". And I read all columns out into a Map then tries to use objectMapper.convertValue to make into a Java Object.
So here's a quick snippet to recreate the problem. Do note I don't control how the Map is generated in the actual code.
#Data
public class Main {
private List<Integer> bar;
public static void main(String[] args) throws Exception{
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> objectMap = new HashMap<String, Object>();
objectMap.put("bar", "[1,2,3]");
// Main foo = objectMapper.convertValue(objectMap, Main.class)
String json = objectMapper.writeValueAsString(objectMap);
Main foo = objectMapper.readValue(json, Main.class);
System.out.println(foo.getBar());
}
}
But this is not right. Instead of parsing the string, ObjectMapper tries to convert String to List directly and failed. I would expect foo.getBar() returns a List with 3 elements, but the code already failed at converting stage.
You should make "[1,2,3]" as String[] so:
"[1,2,3]".replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\s", "").split(",")
Something like:
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> objectMap = new HashMap<String, Object>(){{
put("bar", "[1,2,3]".replaceAll("\\[", "")
.replaceAll("]", "")
.replaceAll("\\s", "").split(","));
}};
String json = objectMapper.writeValueAsString(objectMap);
Main foo = objectMapper.readValue(json, Main.class);
System.out.println(foo.getBar());
}
you just need to replace the "[1,2,3]" with what ever you getting it from.
Note: Not sure about the application logic
I am running into a problem where I am trying to include a List as the root node, but I can't seem to be able to get this. Let me explain. Let's say we have a class "TestClass"
class TestClass{
String propertyA;
}
Now, in some utility method this is what I do
String utilityMethod(){
List<TestClass> list = someService.getList();
new ObjectMapper().writeValueAsString(list);
}
The output I am trying to get in JSON is
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
I have tried to use
objMapper.getSerializationConfig().set(Feature.WRAP_ROOT_VALUE, true);
But, I still don't seem to get it right.
Right now, I am just creating a Map < String,TestClass > and I write that to achieve what I am trying to do, which works but clearly this is a hack. Could someone please help me with a more elegant solution? Thanks
Unfortunately, even with the WRAP_ROOT_VALUE feature enabled you still need extra logic to control the root name generated when serializing a Java collection (see this answer for details why). Which leaves you with the options of:
using a holder class to define the root name
using a map.
using a custom ObjectWriter
Here is some code illustrating the three different options:
public class TestClass {
private String propertyA;
// constructor/getters/setters
}
public class TestClassListHolder {
#JsonProperty("ListOfTestClasses")
private List<TestClass> data;
// constructor/getters/setters
}
public class TestHarness {
protected List<TestClass> getTestList() {
return Arrays.asList(new TestClass("propertyAValue"), new TestClass(
"someOtherPropertyValue"));
}
#Test
public void testSerializeTestClassListDirectly() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
System.out.println(mapper.writeValueAsString(getTestList()));
}
#Test
public void testSerializeTestClassListViaMap() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final Map<String, List<TestClass>> dataMap = new HashMap<String, List<TestClass>>(
4);
dataMap.put("ListOfTestClasses", getTestList());
System.out.println(mapper.writeValueAsString(dataMap));
}
#Test
public void testSerializeTestClassListViaHolder() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final TestClassListHolder holder = new TestClassListHolder();
holder.setData(getTestList());
System.out.println(mapper.writeValueAsString(holder));
}
#Test
public void testSerializeTestClassListViaWriter() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final ObjectWriter writer = mapper.writer().withRootName(
"ListOfTestClasses");
System.out.println(writer.writeValueAsString(getTestList()));
}
}
Output:
{"ArrayList":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
Using an ObjectWriter is very convenient - just bare in mind that all top level objects serialized with it will have the same root name. If thats not desirable then use a map or holder class instead.
I'd expect the basic idea to be something like:
class UtilityClass {
List listOfTestClasses;
UtilityClass(List tests) {
this.listOfTestClasses = tests;
}
}
String utilityMethod(){
List<TestClass> list = someService.getList();
UtilityClass wrapper = new UtilityClass(list);
new ObjectMapper().writeValueAsString(wrapper);
}