Skip to content

Commit f9d8712

Browse files
committed
Add JsonUtils in particular @JsonIo annotation
Typed<T> instances of any T with a @JsonIo-annotated adapter are serialized as json objects with a "type" attribute with the value of the JsonIo.type() annotation, and an "obj" attribute, which is serialized using the annotated adapter. For deserialization, the correct adapter is looked up via the "type" attribute.
1 parent fde8602 commit f9d8712

File tree

2 files changed

+284
-0
lines changed

2 files changed

+284
-0
lines changed

src/main/java/bdv/img/remote/AffineTransform3DJsonSerializer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import java.lang.reflect.Type;
3232

33+
import bdv.tools.JsonUtils;
3334
import net.imglib2.realtransform.AffineTransform3D;
3435

3536
import com.google.gson.JsonDeserializationContext;
@@ -39,6 +40,7 @@
3940
import com.google.gson.JsonSerializationContext;
4041
import com.google.gson.JsonSerializer;
4142

43+
@JsonUtils.JsonIo( jsonType = "AffineTransform3D.Anchor", type = AffineTransform3D.class )
4244
public class AffineTransform3DJsonSerializer implements JsonDeserializer< AffineTransform3D >, JsonSerializer< AffineTransform3D >
4345
{
4446
@Override
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
package bdv.tools;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
import java.lang.reflect.Type;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Objects;
12+
import java.util.concurrent.ConcurrentHashMap;
13+
14+
import org.scijava.Priority;
15+
import org.scijava.annotations.Index;
16+
import org.scijava.annotations.IndexItem;
17+
import org.scijava.annotations.Indexable;
18+
19+
import com.google.gson.Gson;
20+
import com.google.gson.GsonBuilder;
21+
import com.google.gson.JsonArray;
22+
import com.google.gson.JsonDeserializationContext;
23+
import com.google.gson.JsonDeserializer;
24+
import com.google.gson.JsonElement;
25+
import com.google.gson.JsonObject;
26+
import com.google.gson.JsonSerializationContext;
27+
import com.google.gson.JsonSerializer;
28+
29+
public class JsonUtils
30+
{
31+
public static Gson gson()
32+
{
33+
GsonBuilder gsonBuilder = new GsonBuilder();
34+
JsonIos.registerTypeAdapters( gsonBuilder );
35+
return gsonBuilder.create();
36+
}
37+
38+
/**
39+
* Annotation for classes that de/serialize instances of {@code T} from/to JSON.
40+
*/
41+
@Retention( RetentionPolicy.RUNTIME )
42+
@Target( ElementType.TYPE )
43+
@Indexable
44+
public @interface JsonIo
45+
{
46+
/**
47+
* The value of the "type" attribute in serialized instances.
48+
* <p>
49+
* This should be unique, because it is used to pick the correct {@code
50+
* JsonIo} for deserialization of polymorphic {@link Typed} attributes.
51+
*/
52+
String jsonType();
53+
54+
/**
55+
* The class of un-serialized instances.
56+
*/
57+
Class< ? > type();
58+
59+
double priority() default Priority.NORMAL;
60+
}
61+
62+
/**
63+
* {@code Typed<T>} instances of any {@code T} with a {@code @JsonIo}-
64+
* annotated adapter are serialized as json objects with a "type" attribute
65+
* with the value of the {@link JsonIo#type()} annotation, and an "obj"
66+
* attribute, which is serialized using the annotated adapter. For
67+
* deserialization, the correct adapter is looked up via the "type"
68+
* attribute.
69+
*/
70+
public static class Typed< T >
71+
{
72+
private final T obj;
73+
74+
Typed( T obj )
75+
{
76+
Objects.requireNonNull( obj );
77+
this.obj = obj;
78+
}
79+
80+
public T get()
81+
{
82+
return obj;
83+
}
84+
85+
@Override
86+
public String toString()
87+
{
88+
return "Typed{" +
89+
"obj=" + obj +
90+
'}';
91+
}
92+
93+
@JsonIo( jsonType = "JsonUtils.Typed", type = Typed.class )
94+
static class JsonAdapter implements JsonSerializer< Typed< ? > >, JsonDeserializer< Typed< ? > >
95+
{
96+
@Override
97+
public Typed< ? > deserialize(
98+
final JsonElement json,
99+
final Type typeOfT,
100+
final JsonDeserializationContext context )
101+
{
102+
final JsonObject obj = json.getAsJsonObject();
103+
final String jsonType = obj.get( "type" ).getAsString();
104+
final Type type = JsonIos.typeFor( jsonType );
105+
return typed( context.deserialize( obj.get( "data" ), type ) );
106+
}
107+
108+
@Override
109+
public JsonElement serialize(
110+
final Typed< ? > src,
111+
final Type typeOfSrc,
112+
final JsonSerializationContext context )
113+
{
114+
final JsonObject obj = new JsonObject();
115+
final String jsonType = JsonIos.jsonTypeFor( src.get().getClass() );
116+
obj.addProperty( "type", jsonType );
117+
obj.add( "data", context.serialize( src.get() ) );
118+
return obj;
119+
}
120+
}
121+
}
122+
123+
public static class TypedList< T >
124+
{
125+
private final List< T > list;
126+
127+
public TypedList()
128+
{
129+
list = new ArrayList<>();
130+
}
131+
132+
public TypedList( final List< T > list )
133+
{
134+
this.list = list;
135+
}
136+
137+
public List< T > list()
138+
{
139+
return list;
140+
}
141+
142+
@Override
143+
public String toString()
144+
{
145+
return "TypedList{" + list + '}';
146+
}
147+
148+
149+
@JsonUtils.JsonIo( jsonType = "TypedList", type = TypedList.class )
150+
static class JsonAdapter implements JsonDeserializer< TypedList< ? > >, JsonSerializer< TypedList< ? > >
151+
{
152+
@Override
153+
public TypedList< ? > deserialize(
154+
final JsonElement json,
155+
final Type typeOfT,
156+
final JsonDeserializationContext context )
157+
{
158+
return deserializeT( json, typeOfT, context );
159+
}
160+
161+
private < T > TypedList< T > deserializeT(
162+
final JsonElement json,
163+
final Type typeOfT,
164+
final JsonDeserializationContext context )
165+
{
166+
final List< T > list = new ArrayList<>();
167+
for ( JsonElement element : json.getAsJsonArray() )
168+
{
169+
final Typed< T > typed = context.deserialize( element, Typed.class );
170+
list.add( typed.get() );
171+
}
172+
return new TypedList<>( list );
173+
}
174+
175+
@Override
176+
public JsonElement serialize(
177+
final TypedList< ? > src,
178+
final Type typeOfSrc,
179+
final JsonSerializationContext context )
180+
{
181+
final JsonArray array = new JsonArray();
182+
for ( final Object spec : src.list() ) {
183+
array.add( context.serialize( typed( spec ) ) );
184+
}
185+
return array;
186+
}
187+
}
188+
}
189+
190+
/**
191+
* Wrap {@code obj} into a {@code Typed<T>} for serialization of polymorphic objects.
192+
*/
193+
public static < T > Typed< T > typed( T obj )
194+
{
195+
return new Typed<>( obj );
196+
}
197+
198+
static class JsonIos
199+
{
200+
static void registerTypeAdapters( final GsonBuilder gsonBuilder )
201+
{
202+
build();
203+
type_to_JsonAdapterClassName.forEach( ( type, adapterClassName ) -> {
204+
if ( adapterClassName == null )
205+
{
206+
throw new RuntimeException( "could not find JsonAdapter for " + type );
207+
}
208+
209+
final Object adapter;
210+
try
211+
{
212+
adapter = Class.forName( adapterClassName ).newInstance();
213+
}
214+
catch ( final Exception e )
215+
{
216+
throw new RuntimeException( "could not create \"" + adapterClassName + "\" instance", e );
217+
}
218+
gsonBuilder.registerTypeAdapter( type, adapter );
219+
} );
220+
}
221+
222+
private static String jsonTypeFor( final Type type )
223+
{
224+
build();
225+
final String jsonType = type_to_JsonType.get( type );
226+
if ( jsonType == null )
227+
throw new RuntimeException( "could not find JsonIo implementation for " + type );
228+
return jsonType;
229+
}
230+
231+
private static Type typeFor( final String jsonType )
232+
{
233+
build();
234+
final Type type = jsonType_to_Type.get( jsonType );
235+
if ( type == null )
236+
throw new RuntimeException( "could not find JsonIo implementation for " + jsonType );
237+
return type;
238+
}
239+
240+
private static final Map< Type, String > type_to_JsonType = new ConcurrentHashMap<>();
241+
private static final Map< String, Type > jsonType_to_Type = new ConcurrentHashMap<>();
242+
private static final Map< Type, String > type_to_JsonAdapterClassName = new ConcurrentHashMap<>();
243+
244+
private static volatile boolean buildWasCalled = false;
245+
246+
private static void build()
247+
{
248+
if ( !buildWasCalled )
249+
{
250+
synchronized ( JsonUtils.class )
251+
{
252+
if ( !buildWasCalled )
253+
{
254+
try
255+
{
256+
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
257+
final Index< JsonIo > annotationIndex = Index.load( JsonUtils.JsonIo.class, classLoader );
258+
for ( final IndexItem< JsonIo > item : annotationIndex )
259+
{
260+
final JsonUtils.JsonIo io = item.annotation();
261+
type_to_JsonType.put( io.type(), io.jsonType() );
262+
jsonType_to_Type.put( io.jsonType(), io.type() );
263+
type_to_JsonAdapterClassName.put( io.type(), item.className() );
264+
}
265+
}
266+
catch ( final Exception e )
267+
{
268+
throw new RuntimeException( "problem accessing annotation index", e );
269+
}
270+
buildWasCalled = true;
271+
}
272+
}
273+
}
274+
}
275+
}
276+
277+
public static String prettyPrint( final JsonElement jsonElement )
278+
{
279+
Gson gson = new GsonBuilder().setPrettyPrinting().create();
280+
return gson.toJson( jsonElement );
281+
}
282+
}

0 commit comments

Comments
 (0)