Consider example:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import java.io.File;
import java.io.FileInputStream;
public class Exmaple {
public static void main (String[] args) throws Exception {
Object object = new Object() {
private String id = "someId";
};
final JsonGenerator generator = new JsonFactory().createGenerator(System.out);
generator.writeStartObject();
generator.writeFieldName("data");
generator.writeBinary(new FileInputStream(new File("D:\\!Temp\\elasticsearch\\pdf.pdf")), -1);
generator.writeEndObject();
generator.writeObject(object);
generator.close();
}
}
The goal is to add all fields from object during json streaming. With that code I got:
{
"data": "JVBERi0xLjMKJ="
} {
"id": "someId",
}
How tell jackson to not write {} from object?
UPDATED
The exprected result is (e.g. data + all keys from java object):
{
"data": "JVBERi0xLjMKJ=",
"id": "someId",
}
Just don't call writeEndObject() after you call writeBinary It should look like:
final JsonGenerator generator = new JsonFactory().createGenerator(System.out);
generator.writeStartObject();
generator.writeFieldName("data");
generator.writeBinary(new FileInputStream(new File("D:\\!Temp\\elasticsearch\\pdf.pdf")), -1);
generator.writeObject(object);
generator.writeEndObject();
generator.close();
The JsonSerializer type has an unwrappingSerializer method that "unwraps" the serializer. For the BeanSerialier (which is the serializer used for writing objects), the returned serializer will simply not write the start and end of the object.
The provided example, rewritten to use this, looks like this:
final SerializerProvider serializerProvider = /* obtain serializer provider */;
final JsonSerializer<Object> objectSerializer = serializerProvider.findValueSerializer(object.getClass());
final JsonSerializer<Object> unwrappingSerializer = objectSerializer.unwrappingSerializer(NameTransformer.NOP);
final JsonGenerator generator = new JsonFactory().createGenerator(System.out);
generator.writeStartObject();
generator.writeFieldName("data");
generator.writeBinary(new FileInputStream(new File("D:\\!Temp\\elasticsearch\\pdf.pdf")), -1);
unwrappingSerializer.serialize(object, generator, serializerProvider);
generator.close();
Obtaining the SerializerProvider depends on the actual implementation, as the provided example does not use an ObjectCodec. Optimally, this code would live inside the serialize method of a custom serializer, where the SerializerProvider is given as an argument.
The NameTransformer passed to unwrappingSerializer can be used to easily map the names, for example by prefixing them. Using NameTransformer.NOP just writes the names as they are written by the original serializer.
You basically want to merge object variable into the generator's target object. If you can access object variable and you know its properties than you can simply use generator's methods to write them, e.g.:
generator.writeStringField("id", object.id)
Some properties might be complex objects (aka POJO) where you'll have to use writePOJOField(String fieldName, Object pojo); for writePOJOField to work you'll have to properly instantiate your JsonFactory, e.g. new JsonFactory(new ObjectMapper()). See also https://stackoverflow.com/a/73702885/4681265.
The other more generic approach is to generate the object JSON and then write it into generator's output using:
// see http://fasterxml.github.io/jackson-core/javadoc/2.13/
generator.writeRaw(char[] text, int offset, int len)
by skipping the object JSON's opening and enclosing braces.
Related
I'm using the com.fasterxml.jackson.databind.ObjectMapper in jackson-databind 2.11.2 and trying to serialize Java properties with null-value to something like this:
{ %Key% : "" }
I've tried:
ObjectMapper MAPPER = new JodaMapper();
DefaultSerializerProvider defaultSerializerProvider = new DefaultSerializerProvider.Impl();
defaultSerializerProvider.setNullValueSerializer(new JsonSerializer<Object>() {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("bla");
}
});
MAPPER.setSerializerProvider(defaultSerializerProvider);
But the NullValueSerializers serialize-method does not get triggered for any fields.
Has anybody some ideas?
I found the solution.... I had
#JsonInclude(JsonInclude.Include.NON_NULL)
at class level in the class that I wanted to serialize. When I remove the annotation I the code above works.
There are a couple of ways to achieve custom null value serialising:
If you want to serialise a null as an empty String, try using this annotation on a property, or setter:
#JsonSetter(nulls=Nulls.AS_EMPTY)
or the same for specific mapper:
MAPPER.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
You can initialise properties with default values on the declaration or in the getter.
As you've already mentioned, by providing a custom serialiser.
I did try your code, and that serialised null value as expected when using an ObjectMapper instead of JodaMapper. Is there any particular reason for using a JodaMapper?
I'm having a map that I need to serialize but when I serialize it I get jackson infinite recursion (stackoverflowerror). So, after debugging a while I found the entry that caused this problem but this entry is a list which contains other objects that have some cyclic dependencies.
Let me write a sample code here.
Map<String, ?> params = new HashMap();
// Create some list and add objects with cyclic dependencies to it.
List<ContentObject> problematicList = new ArrayList();
//list.addAll(some problematic objects);
params.put("contentsModified", problematicList);
objectmapper.writeValueAsString(params); // here I get jackson infinite recursion (stackoverflowerror)
I tried marking the classes of the objects I found in the problematicList using #JsonIgnoreType but it didn't work. Is there any way to write some custom serializer to see if a map contains an entry with the name contentsModified and if it finds such entry it doesn't serialize the problematic lists?
You should fix the actual bi-directional mapping with problematic classes when serializing. For example if you have classes ContentObject & BackReferringObject referring to each other you can use #JsonBackReference like:
public class ContentObject {
private String name = "contentsModified";
private BackReferringObject backReferringObject;
}
public class BackReferringObject {
#JsonBackReference
private ContentObject contentObject;
}
This tells Jackson to not serialize contentObject back again.
If this is not possible you can always write some custom serializer. That would make things more complicated. For example, you could have something like this:
public class ContentObjectSerializer extends JsonSerializer<ContentObject> {
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public void serialize(ContentObject value, JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
if (!value.getName().equals("contentsModified")) {
var sw = new StringWriter();
objectMapper.writeValue(sw, value);
gen.writeRawValue(sw.toString());
}
}
}
and usage:
var objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(ContentObject.class, new ContentObjectSerializer());
objectMapper.registerModule(module);
The latter configuration is really needed. You cannot for example annotate your ContentObject with #JsonSerialize(using = ContentObjectSerializer.class) since then the ObjectMapper inside custom serializer would cause another recursive call chain to custom serializer and cause stack overflow again.
I realize this is not directly applicable to a Map but as an example
I have a custom serialiser which extends JsonSerializer<T>
and in the ObjectMapper I have included setSerializationInclusion(JsonInclude.Include.NON_NULL).
I still see null field in response.
Currently, I ignore them by checking null for each property. I have almost 15 objects and it's very difficult to add null checking to each property. Object I am using is shared by my applications, that is the reason why I am using custom serialiser to name the properties
#Override
public void serialize(Person personBean, JsonGenerator jgen, SerializerProvider provider) throws IOException {
if(personBean.getFirstName() != null){
jgen.writeStringField("firstname", personBean.getFirstName() );
}
//etc...
}
How to avoid null check for each property and implement some generic code to avoid null values in my serialised response.
Unfortunately, when we write custom serialiser we need to take care about null values by ourself. To make it at least a little bit better we can add new writeStringField method and use it. For example:
class PersonJsonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
writeStringField(gen, "firstname", value.getFirstName());
gen.writeEndObject();
}
private void writeStringField(JsonGenerator gen, String fieldName, String value) throws IOException {
if (value != null) {
gen.writeStringField(fieldName, value);
}
}
}
If you need to change only property names you can use PropertyNamingStrategy option. There is a few possibilities like:
LOWER_CASE - Naming convention in which all words of the logical name are in lower case, and no separator is used between words.
KEBAB_CASE - Naming convention used in languages like Lisp, where words are in lower-case letters, separated by hyphens.
For more check documentation
Example ObjectMapper customisation could look like:
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
If there is no predefined strategy which satisfies your need you can use JsonView annotation.
I have a slightly odd question. I have created an object, let's call it a Profile, that successfully parses single JSON objects via an API that I call. There is also a multi-profile interface that will return a JSON array of Profile objects. The problem is, the multi-profile interface turns the sub objects into strings. Is there an automatic way I can tell jackson to parse these into objects?
Example of a single object:
{ "foo": "bar" }
Example of a multi object:
[ "{ \"foo\": \"bar\" }", "{ \"blah\": \"ugh\" }" ]
(Sorry can't use real data)
Notice that the sub objects are actually strings, with escaped quotes inside them.
For completeness, my code for the multi object parse looks like this:
ObjectMapper mapper = new ObjectMapper();
Profile[] profile_array = mapper.readValue(response.content, Profile[].class);
for (Profile p: profile_array)
{
String user = p.did;
profiles.put(user, p);
}
As I said, in the single-profile case, the Profile object parses. In the multi-profile case, I get this exception:
Exception: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.xyz.id.profile.Profile, problem: no suitable creator method found to deserialize from JSON String
I suppose you'll have to create a custom deserializer and apply it to the every element of that array.
class MyCustomDeserializer extends JsonDeserializer<Profile> {
private static ObjectMapper om = new ObjectMapper();
#Override
public Profile deserialize(JsonParser jp, DeserializationContext ctxt) {
// this method is responsible for changing a single text node:
// "{ \"foo\": \"bar\" }"
// Into a Profile object
return om.readValue(jp.getText(), Profile.class);
}
}
There is no out-of-the-box support for "re-parsing" of embedded JSON-in-JSON content.
But this sounds like a possible request for enhancement (RFE)...
Have you tried using JAXB?
final ObjectMapper mapper = new ObjectMapper();
// Setting up support of JAXB
final AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
// make deserializer use JAXB annotations (only)
mapper.getDeserializationConfig().setAnnotationIntrospector(
introspector);
// make serializer use JAXB annotations (only)
mapper.getSerializationConfig().setAnnotationIntrospector(
introspector);
final StringReader stringReader = new StringReader(response);
respGetClasses = mapper.readValue(stringReader,
FooBarClass.class);
The above should get you started...
Also, you would need to mark each subclass like so:
#XmlElement(name = "event")
public List<Event> getEvents()
{
return this.events;
}
Need your help on conversion of java objects to json.
current the json result showing all the key in small letter case, i need it to be upper case.
ObjectMapper mapper = new ObjectMapper();
Writer strWriter = new StringWriter();
mapper.writeValue(strWriter, obj);
String jsonString= strWriter.toString();
and the result is
[{"flags":"1","name":"Peter","location":"London","startDate":"2012-01-06 00:00"}]
but i want results like this (all key key value should be in UPPER CASE):
[{"FLAGS":"YU","NAME":"Peter","LOCATION":"London","STARTDATE":"2012-01-06 00:00"}]
and also is it possible to get like this also (key first letter in upper case):
[{"Flags":"1","Name":"Peter","Location":"London","StartDate":"2012-01-06 00:00"}]
Can anyone help me on this.
Thanks in advance.
There are multiple ways to do it with Jackson.
Annotations
You could annotate your object with #JsonProperty annotations on your fields or on your getter methods.
Example:
#JsonProperty("Name")
public final String name;
#JsonProperty("Location")
public String getLocation() {
return location;
}
Implement JsonSerializableWithType interface
#Override
public void serialize(final JsonGenerator jG, final SerializerProvider p)
throws IOException, JsonProcessingException
{
serializeWithType(jG, p, null);
}
#Override
public void serializeWithType(final JsonGenerator jG, final SerializerProvider p, final TypeSerializer typeSer)
throws IOException, JsonProcessingException
{
// here you can do your own serialization
}
I would advice to use the #JsonNaming annotation on class level.
Yet afaik there is no strategy out there to fit your needs for total uppercase. But you can probably just write your own.
In my case I needed first character uppercase. So I used the existing
#JsonNaming(value = UpperCamelCaseStrategy.class).