Jackson JSON: Dynamische Serialisierung

GRudiD

Aktives Mitglied
Hallo,

ich nutze in meinem Projekt die Jackson JSON Bibliothek zum Serialisieren von POJOs. Ich möchte die Serialisierung nun so anpassen, dass nur das Top-Level-Objekt (implements Id) komplett serialisiert wird und alle enthaltenen Objekte, die vom Interface Id erben, nur mit dem Wert id erscheinen. Beispiel:

Java:
interface Id {
	public long getID();
}
	
class A implements Id {
	public String name = "test";
	public B child = new B();
		
	public long getID() {
		return 1;
	}
}
	
class B implements Id {
	public long size = 100;
	
	public long getID() {
		return 2;
	}
}

Die normale Ausgabe als JSON entspricht:
Code:
{
	"id": 1,
	"name": "test",
	"child": {
		"id": 2,
		"size": 100
	}
}

Die gewünschte Ausgabe als JSON sollte aber wie folgt sein:
Code:
{
	"id": 1,
	"name": "test",
	"child": {
		"id": 2
	}
}

Ich habe bereits viel recherchiert aber bisher keine guten Anhaltspunkte gefunden das Problem elegant zu lösen. Der Artikel JacksonSampleCustomViewProcessing - FasterXML Wiki beschreibt im Grunde genommen das gleiche Problem, ist aber mit der aktuellen Version der Bibliothek nicht mehr kompilierbar.

Kennt sich jemand mit der Jackson Bibliothek aus und kann mir ein paar Hinweise liefern? Danke!
 
Zuletzt bearbeitet:
Einen Schritt weiter bin ich. Mit dem folgenden Filter kann ich die nicht-id Felder, deren Parent nicht Root ist herausfiltern.
Java:
@JsonFilter("myFilter")
class A implements Id {
	...
}

@JsonFilter("myFilter")
class B implements Id {
	...
}

PropertyFilter filter = new SimpleBeanPropertyFilter() {
	@Override
	public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
		if (pojo instanceof Id) {
			if (include(writer)) {
				if (jgen.getOutputContext().getParent().inRoot()) {
					// is root element
					writer.serializeAsField(pojo, jgen, provider);
				} else if ("id".equals(writer.getName()) {
					// is id
					writer.serializeAsField(pojo, jgen, provider);
				} else {
					writer.serializeAsOmittedField(pojo, jgen, provider);
				}
			} else if (!jgen.canOmitFields()) { // since 2.3
				writer.serializeAsOmittedField(pojo, jgen, provider);
			}
		} else {
			writer.serializeAsField(pojo, jgen, provider);
		}
	}

	@Override
	protected boolean include(BeanPropertyWriter writer) {
		return true;
	}

	@Override
	protected boolean include(PropertyWriter writer) {
		return true;
	}
};

FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", filter);
System.out.println(new ObjectMapper().writer(filters).writeValueAsString(a));

Wenn ich ein Id Objekt jetzt allerdings in Arrays verschachtelt in Maps habe, funktioniert das ganze nicht mehr.
 
Hi,

danke für die Antwort. Leider löst das nicht mein Problem. Beispiel:
Java:
class Node implements Id {
	public long id = 1;
	public String name = "test";
	public Node[] children;
	...
}

Ich möchte erreichen, dass die Klasse Node folgendermaßen dynamisch serialisiert wird:
Code:
{
	"id": 1,
	"name": "test",
	"children": [
		{ "id": 2 },
		{ "id": 3 }
	]
}

Wenn ich nun aber überall ein @JsonIgnore drüberschreibe, würde ich ja immer nur folgendes erhalten:
Code:
{
	"id": 1
}
 
Ich dachte du hast unterschiedliche Klassen? In deinem Bsp. von ganz oben musst du doch nur noch in der Klasse B ein @JsonIgnore an die size-Property machen und fertig... Oder hat sich dein Klassenaufbau zu einer Rekursion verändert???
 
Hi,

mein zweites Beisiel sollte nur verdeutlichen, dass ich nicht einfach ein @JsonIgnore nutzen kann, da ich die Objekte dynamisch zusammenbaue und nicht weiß, welche davon Root und weche davon Child werden. Zudem kann es sein, dass bei einem anderen Aufruf ein Child-Objekt auf einmal Root wird. Trotzdem schonmal danke für deine Hilfe.

Ich hatte die Woche über leider keine Zeit an dem Problem zu arbeiten. Aber vielleicht heute abend 🙂
 
Hi,

ich habe jetzt vorerst eine Lösung gefunden. Ich bin mit dieser allerdings noch nicht ganz zufrieden, da ich hiermit mehr oder weniger an jackson vorbeiprogrammiere. Bedeutet z.B. dass ich neue jackson Annotationen erst in meinem Serializer nachprogrammieren muss.

Java:
public class IdSerializer extends StdSerializer<Id> {
	public IdSerializer() {
		super(Id.class);
	}
	
	private void serialize(Id value, JsonGenerator jgen) throws IOException {
		jgen.writeStartObject();
		jgen.writeObjectField("id", value.getId());
		jgen.writeEndObject();
	}

	private void serialize(Collection<?> values, JsonGenerator jgen) throws IOException {
		jgen.writeStartArray();
		for (Object value : values) {
			if (value instanceof Id) {
				this.serialize((Id) value, jgen);
			} else {
				jgen.writeObject(value);
			}
		}
		jgen.writeEndArray();
	}

	private void serialize(Map<?, ?> values, JsonGenerator jgen) throws IOException {
		jgen.writeStartObject();
		for (Object key : values.keySet()) {
			Object value = values.get(key);

			jgen.writeFieldName(String.valueOf(key));
			if (value instanceof Id) {
				this.serialize((Id) value, jgen);
			} else {
				jgen.writeObject(value);
			}
		}
		jgen.writeEndObject();
	}

	@Override
	public void serialize(Id value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
		jgen.writeStartObject();
		for (Field field : value.getClass().getDeclaredFields()) {
			// ignore static fields
			if (Modifier.isStatic(field.getModifiers()))
				continue;
			
			// ignore fields tagged with @JsonIgnore
			JsonIgnore jsonIgnore = field.getAnnotation(JsonIgnore.class);
			if (jsonIgnore != null && jsonIgnore.value())
				continue;
			
			// access private fields
			field.setAccessible(true);
			
			Object obj = null;
			try {
				obj = field.get(value);
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
				continue;
			}

			String fieldName = field.getName();
			JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
			if (jsonProperty != null && jsonProperty.value() != null) {
				fieldName = jsonProperty.value();
			}
			jgen.writeFieldName(fieldName);

			if (obj instanceof Id) {
				this.serialize((Id) obj, jgen);
			} else if (field.getType().isArray()) {
				this.serialize(Arrays.asList((Object[]) obj), jgen);
			} else if (Collection.class.isAssignableFrom(field.getType())) {
				this.serialize((Collection<?>) obj, jgen);
			} else if (Map.class.isAssignableFrom(field.getType())) {
				this.serialize((Map<?,?>) obj, jgen);
			} else {
				jgen.writeObject(obj);
			}
		}
		jgen.writeEndObject();
	}
}
 

Neue Themen


Zurück
Oben