I have a class:
class Setting {
String configurationName;
String configuration;
}
I want to return the string representation how configuration will look like. This can be different object based on some conditions.
In one of service I do below :
#GET
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response getConfigurationSetting ()
{
try {
Setting settingPojo = new Setting();
settingPojo.setCOnfigurationName("DataBase");
ObjectMapper mapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(OracleConfiguration.class, visitor);
JsonSchema schema = visitor.finalSchema();
settingPojo.setConfiguraton(mapper.writeValueAsString(schema));
return Response.status(HTTP.CodeOK).entity(settingPojo).build();
} catch (Exception ex) {
// exception logic
}
}
There can be different class like this : "MySQLConfiguration.class".
Sample :
{
configurationName : "DataBase",
configuration: "{
\"type\":\"object\",
\"id\":\"urn\":\"jsonschema\":\"database\":\"model\":\"OracleConfiguration\",
\"properties\":{
\"numberOfConnection\":{
\"type\":\"integer\"
},
\"connectionDate\":{
\"type\":\"integer\",
\"format":\"utc-millisec\"
},
\"isconnected\":{
\"type\":\"boolean\"
}
}
}"
}
Problems with above output:
I want to remove id property from the string.
I am getting that weird extra backslash for escape character. I do not see that while debugging and executing this line : mapper.writeValueAsString(schema).But I see that backslash and extra quotes after I set to property.
Any idea how to resolve these?
You can instruct Jackson to remove id using MixIn feature. Take a look at the example.
Object is serialised twice, once directly by you in the controller method, second - by Spring.
In your example you need to:
Use #JsonRawValue annotation in Setting class:
class Setting {
String configurationName;
#JsonRawValue
String configuration;
}
Create a MixIn abstract class:
abstract class JsonSchemaWithoutId {
#JsonIgnore
public String id;
#JsonIgnore
public abstract String getId();
}
Simple usage:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper;
import lombok.Data;
import java.io.File;
import java.io.IOException;
public class JsonMixInAndSchemaApp {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.addMixIn(JsonSchema.class, JsonSchemaWithoutId.class);
Setting settingPojo = new Setting();
settingPojo.setConfigurationName("Setting");
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(Setting.class, visitor);
JsonSchema schema = visitor.finalSchema();
settingPojo.setConfiguration(mapper.writeValueAsString(schema));
mapper.writeValue(System.out, settingPojo);
}
}
Above code prints
{
"configurationName" : "Setting",
"configuration" : {
"type" : "object",
"properties" : {
"configurationName" : {
"type" : "string"
},
"configuration" : {
"type" : "string"
}
}
}
}
Related
Here my POJO:
public class AutorDenormalized {
private String id;
private Long unitatId;
private String grupId;
private String descripcio;
public AutorDenormalized() {
}
// getters $ setters
}
I'd like to serialise this kind of objects adding a suffix according to field type. I mean,
If field type is a String -> then add a *_s suffix
If field type is a Long -> then add a *_l suffix
Otherwise keep going
Do you have any ideas how to solve it?
You need to implement custom BeanPropertyWriter which can generate property name with a suffix. To register custom BeanPropertyWriter you need to create custom BeanSerializerModifier.
Below example shows simplified implementation which shows a way how to achieve above result:
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.util.NameTransformer;
import java.io.IOException;
import java.util.List;
public class JsonTypeInfoApp {
public static void main(String[] args) throws IOException {
SimpleModule typeSuffixModule = new SimpleModule();
typeSuffixModule.setSerializerModifier(new TypeSuffixBeanSerializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(typeSuffixModule);
System.out.println(mapper.writeValueAsString(new AutorDenormalized()));
}
}
class TypeSuffixBeanSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); ++i) {
final BeanPropertyWriter writer = beanProperties.get(i);
Class<?> rawType = writer.getType().getRawClass();
if (supports(rawType)) {
final String suffix = constructSuffix(rawType);
beanProperties.set(i, writer.rename(NameTransformer.simpleTransformer(null, suffix)));
}
}
return beanProperties;
}
private String constructSuffix(Class<?> rawType) {
return "_" + Character.toLowerCase(rawType.getSimpleName().charAt(0));
}
private boolean supports(Class<?> rawClass) {
return rawClass == String.class || rawClass == Long.class;
}
}
Above code prints:
{
"id_s" : "1",
"unitatId_l" : 123,
"grupId_s" : "2",
"descripcio_s" : "3"
}
See also:
Jackson custom serialization and deserialization
Aside from the accepted answer, which works fine, you could also consider implementing PropertyNameStrategy: it would let you rename properties and gets field, setter/getter, creator parameter (which you need to find type of property). Might be little bit less work.
My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}
I am serializing a class that includes an unmodifiable list with default typing enabled. The problem is that the type that Jackson uses is
java.util.Collections$UnmodifiableRandomAccessList
which, for some reason, the deserializer does not know how to handle.
Is there a way to tell Jackson to set the type as
java.util.ArrayList
which the deserializer does know how to handle, instead? If possible, I'd like to do it using mixins.
Something like
public abstract class ObjectMixin {
#JsonCreator
public ObjectMixin(
#JsonProperty("id") String id,
#JsonProperty("list") #JsonSerialize(as = ArrayList.class) List<String> list;
) {}
}
which, unfortunately, does not work.
I would like to start from security risk warning which comes from ObjectMapper documentation:
Notes on security: use "default typing" feature (see
enableDefaultTyping()) is a potential security risk, if used with
untrusted content (content generated by untrusted external parties).
If so, you may want to construct a custom TypeResolverBuilder
implementation to limit possible types to instantiate, (using
setDefaultTyping(com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder<?)).
Lets implement custom resolver:
class CollectionsDefaultTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
private final Map<String, String> notValid2ValidIds = new HashMap<>();
public CollectionsDefaultTypeResolverBuilder() {
super(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
this._idType = JsonTypeInfo.Id.CLASS;
this._includeAs = JsonTypeInfo.As.PROPERTY;
notValid2ValidIds.put("java.util.Collections$UnmodifiableRandomAccessList", ArrayList.class.getName());
// add more here...
}
#Override
protected TypeIdResolver idResolver(MapperConfig<?> config, JavaType baseType, Collection<NamedType> subtypes,
boolean forSer, boolean forDeser) {
return new ClassNameIdResolver(baseType, config.getTypeFactory()) {
#Override
protected String _idFrom(Object value, Class<?> cls, TypeFactory typeFactory) {
String id = notValid2ValidIds.get(cls.getName());
if (id != null) {
return id;
}
return super._idFrom(value, cls, typeFactory);
}
};
}
}
Now, we can use it as below:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setDefaultTyping(new CollectionsDefaultTypeResolverBuilder());
Root root = new Root();
root.setData(Collections.unmodifiableList(Arrays.asList("1", "b")));
String json = mapper.writeValueAsString(root);
System.out.println(json);
System.out.println(mapper.readValue(json, Root.class));
}
}
class Root {
private List<String> data;
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
#Override
public String toString() {
return "Root{" +
"data=" + data +
'}';
}
}
Above code prints:
{
"data" : [ "java.util.ArrayList", [ "1", "b" ] ]
}
Root{data=[1, b]}
You can even map it to List interface:
notValid2ValidIds.put("java.util.Collections$UnmodifiableRandomAccessList", List.class.getName());
And output would be:
{
"data" : [ "java.util.List", [ "1", "b" ] ]
}
I have an object that sometimes looks like this:
{
"foo" : "bar",
"fuzz" : "bla"
}
and sometimes looks like this:
{
"foo" : { "value" : "bar", "baz": "asdf" },
"fuzz" : { "thing" : "bla", "blip" : "asdf" }
}
these classes would look like:
public class Foo {
String value;
String baz;
}
public class Fuzz {
String thing;
String blip;
}
where the first cases are shorthand for the second ones. I would like to always deserialize into the second case.
Further - this is a pretty common pattern in our code, so I would like to be able to do the serialization in a generic manner, as there are other classes similar to Foo above that have the same pattern of using String as a syntactic sugar for a more complex object.
I'd imagine the code to use it would look something like this
public class Thing {
#JsonProperty("fuzz")
Fuzz fuzz;
#JsonProperty("foo")
Foo foo;
}
How do I write a custom deserializer (or some other module) that generically handles both cases?
To make it generic we need to be able to specify name which we would like to set in object for JSON primitive. Some flexibility gives annotation approach. Let's define simple annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface JsonPrimitiveName {
String value();
}
Name means: in case primitive will appear in JSON use value() to get property name for given primitive. It binds JSON primitive with POJO field. Simple deserialiser which handles JSON object and JSON primitive:
class PrimitiveOrPojoJsonDeserializer extends JsonDeserializer implements ContextualDeserializer {
private String primitiveName;
private JavaType type;
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(type);
if (p.currentToken() == JsonToken.START_OBJECT) {
return deserializer.deserialize(p, ctxt);
} else if (p.currentToken() == JsonToken.VALUE_STRING) {
BeanDeserializer beanDeserializer = (BeanDeserializer) deserializer;
try {
Object instance = beanDeserializer.getValueInstantiator().getDefaultCreator().call();
SettableBeanProperty property = beanDeserializer.findProperty(primitiveName);
property.deserializeAndSet(p, ctxt, instance);
return instance;
} catch (Exception e) {
throw JsonMappingException.from(p, e.getMessage());
}
}
return null;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
JsonPrimitiveName annotation = property.getAnnotation(JsonPrimitiveName.class);
PrimitiveOrPojoJsonDeserializer deserializer = new PrimitiveOrPojoJsonDeserializer();
deserializer.primitiveName = annotation.value();
deserializer.type = property.getType();
return deserializer;
}
}
Now we need to annotate POJO fields as below:
class Root {
#JsonPrimitiveName("value")
#JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Foo foo;
#JsonPrimitiveName("thing")
#JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Fuzz fuzz;
// getters, setters
}
I assume that all classes are POJO-s and follow all rules - have getters, setters and default constructor. In case constructor does not exist you need to change this beanDeserializer.getValueInstantiator().getDefaultCreator().call() line somehow which fits your requirements.
Example app:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, Root.class));
}
}
Prints for shortened JSON:
Root{foo=Foo{value='bar', baz='null'}, fuzz=Fuzz{thing='bla', blip='null'}}
And for full JSON payload:
Root{foo=Foo{value='bar', baz='asdf'}, fuzz=Fuzz{thing='bla', blip='asdf'}}
How to remove the id field ("id" : "urn:jsonschema:org:gradle:Person")
from JSON schema created using Jackson?
Generated Schema
{
"type" : "object",
"id" : "urn:jsonschema:org:gradle:Person",
"properties" : {
"name" : {
"type" : "string"
}
}
}
For POJO class (Person.class)
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
#JsonProperty("name")
private String name;
}
Using JSON Schema Generator
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
public final class GetJsonSchema {
public static String getJsonSchema2(Class clazz) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper);
JsonSchema jsonSchema = jsonSchemaGenerator.generateSchema(clazz);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
}
}
Invoked like
System.out.println(JsonSchema.Create(Person.class));
Just set id to null.
E.g.:
jsonSchema.setId(null);
As sachin said, jsonSchema.setId(null) is a good way to accomplish your goal. But Venkat is right in that complex types will still have the id's.
One way to remove them is to use a custom SchemaFactoryWrapper, which will instantiate its own visitorContext which will refuse to provide a URN. However, it's important to note this won't work if one type refers to itself (for example, a status object that might have children status objects).
For example:
private static class IgnoreURNSchemaFactoryWrapper extends SchemaFactoryWrapper {
public IgnoreURNSchemaFactoryWrapper() {
this(null, new WrapperFactory());
}
public IgnoreURNSchemaFactoryWrapper(SerializerProvider p) {
this(p, new WrapperFactory());
}
protected IgnoreURNSchemaFactoryWrapper(WrapperFactory wrapperFactory) {
this(null, wrapperFactory);
}
public IgnoreURNSchemaFactoryWrapper(SerializerProvider p, WrapperFactory wrapperFactory) {
super(p, wrapperFactory);
visitorContext = new VisitorContext() {
public String javaTypeToUrn(JavaType jt) {
return null;
}
};
}
}
private static final String printSchema(Class c) {
try {
ObjectMapper mapper = new ObjectMapper();
IgnoreURNSchemaFactoryWrapper visitor = new IgnoreURNSchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(c, visitor);
JsonSchema schema = visitor.finalSchema();
schema.setId(null);
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
String asString = writer.writeValueAsString(schema);
return asString;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}