В моем веб-приложении, использующем Spring, мы хотим использовать пользовательскую структуру JSON. Spring по умолчанию принимает POJO следующим образом:
public class Model {
private int id;
private String name;
public Model(){}
public Model(int id, String name){
this.id = id;
this.name = name;
}
}
и превращает его в это:
{"id":1, "name":"Bob"}
В нашем приложении мы хотим превратить его в это:
[1, "Bob"]
Я хочу использовать логику сериализации Spring по умолчанию, которая определяет тип Java (int, String, Collection и т.д.) И сопоставляет соответствующий тип JSON, но просто меняйте объект обтекания на массив, а не на объект с полями.
Это сериализатор, который у меня есть до сих пор (который будет реализован в модели с помощью @JsonSerialize (с использованием = Serializer.class)), но предпочел бы не переписывать всю логику Spring, которая уже реализована.
public class Serializer extends JsonSerializer<Model> {
@Override
public void serialize(Model value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartArray();
jgen.writeString(value.id);
.... other values ...
jgen.writeEndArray();
}
}
Как я могу подключиться к уже существующему Сериализатору, чтобы этот новый сериализатор работал с любым POJO по умолчанию (а не только с классом Model, но с любым подобным или дочерним классом нам нужно сериализовать массив)? Это может иметь смешанные свойства и никакого специального соглашения об именах для свойств.
Я хочу, чтобы избежать написания пользовательского сериализатора для каждого другого класса Model (... других значений...).
Взгляните на библиотеку Apache BeanUtils, в частности, обратите внимание на метод BeanUtils.populate()
.
То, что этот метод делает, - это преобразовать любой данный Object
в Map<String, Object>
, на основе соглашений JavaBeans. В ключах у вас будут имена атрибутов, тогда как в значениях вы должны иметь каждое значение атрибута. Для стандартных случаев этого метода должно быть достаточно. Внимательно прочитайте документацию, чтобы проверить, как обращаться с особыми случаями.
Model model = ...; // get your model from some place
Map<String, Object> properties = new HashMap<>();
BeanUtils.populate(model, properties);
// Exception handling and special cases left as an excercise
Вышеуказанная рекурсивно заполняет карту properties
, а это означает, что если ваша Model
имеет атрибут otherModel
, тип которого является OtherModel
, тогда карта properties
будет иметь другую карту в записи, которая соответствует otherModel
, и так далее для других вложенных POJO.
Когда у вас есть карта properties
, то, что вы хотите сериализовать, поскольку элементы вашего массива будут в своих значениях. Итак, что-то вроде этого должно выполнить эту работу:
public List<Object> toArray(Map<String, Object> properties) {
List<Object> result = new ArrayList<>();
for (Object obj : properties.values()) {
Object elem = null;
if (obj != null) {
Class<?> clz = obj.getClass();
if (Map.class.isAssignableFrom(clz)) {
elem = toArray((Map<String, Object>) obj); // recursion!
} else {
elem = obj;
}
}
result.add(elem); // this adds null values
// move 1 line up if you don't
// want to serialize nulls
}
return result;
}
Затем, после вызова метода toArray()
, у вас будет List<Object>
готовый к сериализации, используя стандартные механизмы Spring. Я даже считаю, что вам не понадобится определенный сериализатор:
List<Object> array = toArray(properties);
return array; // return this array, i.e. from a Controller
Отказ от ответственности:
Пожалуйста, используйте это как руководство, а не как окончательное решение. Я старался быть как можно более осторожным, но у кода могут быть ошибки. Я вполне уверен, что для обработки массивов и Iterable
POJO требуется специальная обработка. Это, несомненно, отсутствие обработки исключений. Он работает только для POJO. Он может взорваться, если поставляемый объект имеет круглые ссылки. Это не проверено!
Для этого вы можете использовать аннотацию @JsonValue
. Пример:
public class Model {
private int id;
public Model(){}
public Model(int id){
this.id = id;
}
@JsonValue
public int[] getValue() {
return new int[]{this.id};
}
}
@JsonValue
и реализовать этот смешанный интерфейс во всех ваших классах. Более того, вы можете просто указать этот миксин в конфигурации ObjectMapper
и фактически не изменять свои собственные классы.