001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.databind.io.json; 007 008import com.fasterxml.jackson.core.JsonFactory; 009import com.fasterxml.jackson.core.JsonGenerator; 010import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; 011 012import java.io.IOException; 013import java.io.Writer; 014 015import dev.metaschema.core.configuration.IMutableConfiguration; 016import dev.metaschema.core.model.IBoundObject; 017import dev.metaschema.core.util.ObjectUtils; 018import dev.metaschema.databind.io.AbstractSerializer; 019import dev.metaschema.databind.io.SerializationFeature; 020import dev.metaschema.databind.model.IBoundDefinitionModelAssembly; 021import edu.umd.cs.findbugs.annotations.NonNull; 022import nl.talsmasoftware.lazy4j.Lazy; 023 024/** 025 * Provides support for serializing bound Java objects to JSON format based on a 026 * Metaschema module definition. 027 * <p> 028 * This serializer uses Jackson's {@link JsonGenerator} to produce JSON output 029 * that conforms to the Metaschema-defined data structure. 030 * 031 * @param <CLASS> 032 * the Java type of the bound object to be serialized 033 */ 034public class DefaultJsonSerializer<CLASS extends IBoundObject> 035 extends AbstractSerializer<CLASS> { 036 private Lazy<JsonFactory> factory; 037 038 /** 039 * Construct a new Module binding-based deserializer that reads JSON-based 040 * Module content. 041 * 042 * @param definition 043 * the assembly class binding describing the Java objects this 044 * deserializer parses data into 045 */ 046 public DefaultJsonSerializer(@NonNull IBoundDefinitionModelAssembly definition) { 047 super(definition); 048 resetFactory(); 049 } 050 051 /** 052 * Resets the JSON factory to use a freshly created instance. 053 * <p> 054 * This method is called when the serializer configuration changes to ensure the 055 * factory reflects the current settings. 056 */ 057 protected final void resetFactory() { 058 this.factory = Lazy.of(this::newFactoryInstance); 059 } 060 061 @Override 062 protected void configurationChanged(IMutableConfiguration<SerializationFeature<?>> config) { 063 super.configurationChanged(config); 064 resetFactory(); 065 } 066 067 /** 068 * Constructs a new JSON factory. 069 * <p> 070 * Subclasses can override this method to create a JSON factory with a specific 071 * configuration. 072 * 073 * @return the factory 074 */ 075 @NonNull 076 protected JsonFactory newFactoryInstance() { 077 return JsonFactoryFactory.instance(); 078 } 079 080 /** 081 * Get the configured JSON factory instance. 082 * 083 * @return the JSON factory used to create JSON generators 084 */ 085 @NonNull 086 private JsonFactory getJsonFactory() { 087 return ObjectUtils.notNull(factory.get()); 088 } 089 090 /** 091 * Create a new JSON generator for writing to the provided writer. 092 * 093 * @param writer 094 * the writer to send JSON output to 095 * @return a new JSON generator configured with pretty printing 096 * @throws IOException 097 * if an error occurs while creating the generator 098 */ 099 @SuppressWarnings("resource") 100 @NonNull 101 private JsonGenerator newJsonGenerator(@NonNull Writer writer) throws IOException { 102 JsonFactory factory = getJsonFactory(); 103 return ObjectUtils.notNull(factory.createGenerator(writer) 104 .setPrettyPrinter(new DefaultPrettyPrinter())); 105 } 106 107 @Override 108 public void serialize(IBoundObject data, Writer writer) throws IOException { 109 try (JsonGenerator generator = newJsonGenerator(writer)) { 110 IBoundDefinitionModelAssembly definition = getDefinition(); 111 112 boolean serializeRoot = get(SerializationFeature.SERIALIZE_ROOT); 113 if (serializeRoot) { 114 // first write the initial START_OBJECT 115 generator.writeStartObject(); 116 117 generator.writeFieldName(definition.getRootJsonName()); 118 } 119 120 MetaschemaJsonWriter jsonWriter = new MetaschemaJsonWriter(generator); 121 jsonWriter.write(definition, data); 122 123 if (serializeRoot) { 124 generator.writeEndObject(); 125 } 126 } 127 } 128}