tag:blogger.com,1999:blog-53254261855012675992024-03-05T11:31:58.192-06:00Programmer BruceBruce Colemanhttp://www.blogger.com/profile/03248355056606365506noreply@blogger.comBlogger16125tag:blogger.com,1999:blog-5325426185501267599.post-46544835773322647182011-07-11T01:53:00.006-05:002011-07-14T03:46:23.832-05:00Gson v Jackson - Part 6<table border="0"><tbody><tr><td><h2>tl;dnr</h2>Use Jackson, not Gson. Use this article as a reference for basic features.<br /><br /><a target="_blank" href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html">Part 5</a> of this short series of articles ended with section 5.17 of the Gson user guide, "Streaming". (Sections 6 and 7 will not be reviewed.) This sixth part includes a summary of the key differences between Gson and Jackson noted so far, along with a table of contents listing for easy navigation to the various sections of this review.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/0oR3t" alt="QR Code Link To This Article" border="0" />http://goo.gl/0oR3t</div></td></tr></tbody></table><a name='more'></a><br />For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at <a href="https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip">https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip</a>.)<br /><br />This information is based on Gson release 1.7.1 and Jackson release 1.8.2.<br /><br /><h2>The Gson User Guide Walk-through Continued...</h2><br /><h3>Quick Navigation to the Sections of this Review</h3><br /><ul><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Using-Gson"><b>5</b> Using Gson</a><ul><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Primitives-Examples"><b>5.1</b> Primitives Examples</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Object-Examples"><b>5.2</b> Object Examples</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Nested-Classes-including-Inner-Clas"><b>5.3</b> Nested Classes (including Inner Classes)</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Array-Examples"><b>5.4</b> Array Examples</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Collections-Examples"><b>5.5</b> Collections Examples</a><ul><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Collections-Limitations"><b>5.5.1</b> Collections Limitations</a></li></ul></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Serializing-and-Deserializing-Gener"><b>5.6</b> Serializing and Deserializing Generic Types</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Serializing-and-Deserializing-Colle"><b>5.7</b> Serializing and Deserializing Collection with Objects of Arbitrary Types</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-Built-in-Serializers-and-Deserializ"><b>5.8</b> Built-in Serializers and Deserializers</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-Custom-Serialization-and-Deserializ"><b>5.9</b> Custom Serialization and Deserialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-Writing-an-Instance-Creator"><b>5.10</b> Writing an Instance Creator</a><ul><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-InstanceCreator-for-a-Parameterized"><b>5.10.1</b> InstanceCreator for a Parameterized Type</a></li></ul></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Compact-Vs.-Pretty-Printing-for-JSO"><b>5.11</b> Compact Vs. Pretty Printing for JSON Output Format</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Null-Object-Support"><b>5.12</b> Null Object Support</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Versioning-Support"><b>5.13</b> Versioning Support</a></li><li><b>5.14</b> Excluding Fields From Serialization and Deserialization<ul><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Java-Modifier-Exclusion"><b>5.14.1</b> Java Modifier Exclusion</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Gson-s-Expose"><b>5.14.2</b> Gson's @Expose</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-User-Defined-Exclusion-Strategies"><b>5.14.3</b> User Defined Exclusion Strategies</a></li></ul></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-JSON-Field-Naming-Support"><b>5.15</b> JSON Field Naming Support</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-Sharing-State-Across-Custom-Seriali"><b>5.16</b> Sharing State Across Custom Serializers and Deserializers</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-Streaming"><b>5.17</b> Streaming</a></li></ul></li></ul><br /><h3>Summary of the key differences between Gson and Jackson noted so far</h3><br /><b>Comparable Features</b><br /><ul><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Primitives-Examples">COMPARABLE for primitives handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Object-Examples">COMPARABLE for direct field access handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Object-Examples">COMPARABLE for default handling of null</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Object-Examples">COMPARABLE for synthetic field handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Missing-Element-Handling">COMPARABLE for missing element handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Extra-Element-Handling">COMPARABLE for extra element handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Nested-Classes-including-Inner-Clas">COMPARABLE for static nested classes handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Inner-Classes-Serialization">COMPARABLE for inner classes serialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Array-Examples">COMPARABLE for basic array handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Collections-Examples">COMPARABLE for basic collections handling</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Serializing-and-Deserializing-Colle">COMPARABLE for similar support to serialize collections with arbitrary types</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-Custom-Serialization-and-Deserializ">COMPARABLE for basic custom serialization and deserialization support</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-InstanceCreator-for-a-Parameterized">COMPARABLE in ability to deserialize with parameterized types</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-InstanceCreator-for-a-Parameterized">COMPARABLE in ability to accommodate missing JSON element values</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Compact-Vs.-Pretty-Printing-for-JSO">COMPARABLE for built-in pretty printing capability</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Null-Object-Support">COMPARABLE for global on/off configurability of whether to serialize null references</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Java-Modifier-Exclusion">COMPARABLE for global configurability to include fields by modifier</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Gson-s-Expose">COMPARABLE for simple configurability to include only specified fields for participation in serialization/deserialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-User-Defined-Exclusion-Strategies">COMPARABLE for simple configurability of arbitrary logic to exclude fields during serialization and deserialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-JSON-Field-Naming-Support">COMPARABLE for ability to use an annotation to specify per-property translation to JSON element names</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-JSON-Field-Naming-Support">COMPARABLE for ability to specify naming strategy translations for all properties</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-Sharing-State-Across-Custom-Seriali">COMPARABLE for available options to share data between custom serializers and deserializers</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-Streaming">COMPARABLE for ability to read and write JSON one token at a time</a></li></ul><br /><b>Things Gson Does That Jackson Does Not</b><br /><ul><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Primitives-Examples">+1 Gson for simple unwrapping single-component arrays</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Inner-Classes-Deserialization">+1 Gson for simple deserialization of inner classes</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-Writing-an-Instance-Creator">+1 Gson for ability to create Java instances without no-argument constructors and without explicit configuration information</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-InstanceCreator-for-a-Parameterized">+1 Gson for simple, contextual deserialization with parameterized type information</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Versioning-Support">+1 Gson for built-in versioning support</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Java-Modifier-Exclusion">+1 Gson for ability to configure field inclusion by modifiers static, transient, or volatile</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-JSON-Field-Naming-Support">+1 Gson for providing some built-in naming strategies</a></li></ul><br /><b>Things Jackson Does That Gson Does Not</b><br /><ul><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Exception-Handling">+1 Jackson for using checked exceptions for errors during JSON parsing and generation</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Object-Examples">+1 Jackson for very simple deserialization of any JSON object to a Map, and any JSON array to a List, composed of type-appropriate, standard Java library value objects</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Object-Examples">+1 Jackson for providing optional use of getters and setters</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html#TOC-Anonymous-Inner-Classes-Ser">+1 Jackson for anonymous inner classes serialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Array-Examples">+1 Jackson for significantly more complete data-binding support of multi-dimensional arrays</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Array-Examples">+1 Jackson for significantly more complete data-binding support of arbitrarily complex array element types</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Collections-Examples">+1 Jackson for built-in polymorphic deserialization capability</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Serializing-and-Deserializing-Gener">+1 Jackson for significantly better support of deserializing to non-generic collections</a></li><li><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html#TOC-Serializing-and-Deserializing-Colle">+1 Jackson for significantly better support of deserializing to non-generic collections (again)</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html#TOC-Built-in-Serializers-and-Deserializ">+1 Jackson for more built-in serializers and deserializers</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Compact-Vs.-Pretty-Printing-for-JSO">+1 Jackson for allowing easy plug-in of custom pretty printing</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Null-Object-Support">+1 Jackson for greater configurability of null reference serialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Java-Modifier-Exclusion">+1 Jackson for ability to configure field inclusion by default visibility</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Gson-s-Expose">+1 Jackson for simple configurability to exclude only specified fields for participation in serialization/deserialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html#TOC-Gson-s-Expose">+1 Jackson for greater configurability options to include only specified fields for participation in serialization/deserialization</a></li><li><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html#TOC-JSON-Field-Naming-Support">+1 Jackson for providing a facility to specify different translations for fields versus setter methods versus getter methods</a></li></ul><br />To be continued...<br /><br /><h2>References And Resources:</h2><ul><li><a target="_blank" href="http://code.google.com/p/google-gson">Gson Project Home</a></li><li><a target="_blank" href="http://jackson.codehaus.org">Jackson Project Home</a></li><li><a target="_blank" href="https://groups.google.com/forum/#!forum/google-gson">Gson User Group Mailing List</a></li><li><a target="_blank" href="http://jackson-users.ning.com">Jackson JSON User Group</a></li><li><a target="_blank" href="http://code.google.com/p/google-gson/source/checkout">The Gson Source Code</a></li><li><a target="_blank" href="http://svn.codehaus.org/jackson/">The Jackson Source Code</a></li></ul>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com6tag:blogger.com,1999:blog-5325426185501267599.post-1034448894350065522011-07-10T23:12:00.004-05:002011-07-11T05:31:12.610-05:00Gson v Jackson - Part 5<table border="0"><tbody><tr><td><h2>tl;dnr</h2>Use Jackson, not Gson. Use this article as a reference for basic features.<br /><br /><a target="_blank" href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html">Part 4</a> of this short series of articles ended with section 5.14.3 of the Gson user guide, "User Defined Exclusion Strategies". This fifth part continues with section 5.15 on "JSON Field Naming Support", and ends with section 5.17 on "Streaming", which is the end of the Gson user guide. Part 6 will include a summary of the key differences between Gson and Jackson noted so far, along with a table of contents listing for easy navigation to the various sections of this review.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/fWihj" alt="QR Code Link To This Article" border="0" />http://goo.gl/fWihj</div></td></tr></tbody></table><a name='more'></a><br />See <a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-6.html">part 6</a> of this series for a complete listing of and links to the various sections of this Gson user guide review.<br /><br />For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at <a href="https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip">https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip</a>.)<br /><br />This information is based on Gson release 1.7.1 and Jackson release 1.8.2.<br /><br /><h2>The Gson User Guide Walk-through Continued...</h2><br /><a name="TOC-JSON-Field-Naming-Support"><h3>JSON Field Naming Support</h3></a><br />Both Gson and Jackson provide a plug-in facility for users to provide custom translations for converting from a Java property name into a JSON element name, using either single-property-specific annotations or as naming strategies to be applied to all properties. The current release of Gson also includes a few ready-made naming strategies to choose from. The next release of Jackson should as well. The next release of Jackson should also include the ability to provide translations from the JSON element name to the Java property name. (This would make it possible to easily implement case-insensitive name matching, for example. To otherwise do so requires a custom deserializer using either Gson or Jackson.)<br /><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-JSON-Field-Naming-Support">relevant section in the Gson user guide</a>. (The guide demonstrates using a built-in naming policy. To specify a custom one, set an implementation of <code>FieldNamingStrategy</code> with <code>GsonBuilder.setFieldNamingStrategy</code>.)<br /><br /><b>The comparable Jackson Code:</b><pre name="code" class="java">public class JacksonPropertyTranslationSupportDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> SomeObject someObject = new SomeObject("first", "second");<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.setPropertyNamingStrategy(<br /> new UpperCamelCaseNamingStrategy());<br /> System.out.println(mapper.writeValueAsString(someObject));<br /> // {"custom_naming":"first","SomeOtherField":"second"}<br /> }<br />}<br /><br />class SomeObject<br />{<br /> @JsonProperty("custom_naming")<br /> public final String someField;<br /> public final String someOtherField;<br /><br /> public SomeObject(String a, String b)<br /> {<br /> this.someField = a;<br /> this.someOtherField = b;<br /> }<br />}<br /><br />class UpperCamelCaseNamingStrategy<br /> extends PropertyNamingStrategy<br />{<br /> @Override<br /> public String nameForGetterMethod(MapperConfig<?> config,<br /> AnnotatedMethod method, String defaultName)<br /> {<br /> return translate(defaultName);<br /> }<br /><br /> @Override<br /> public String nameForSetterMethod(MapperConfig<?> config,<br /> AnnotatedMethod method, String defaultName)<br /> {<br /> return translate(defaultName);<br /> }<br /><br /> @Override<br /> public String nameForField(MapperConfig<?> config,<br /> AnnotatedField field, String defaultName)<br /> {<br /> return translate(defaultName);<br /> }<br /><br /> private String translate(String defaultName)<br /> {<br /> char[] nameChars = defaultName.toCharArray();<br /> nameChars[0] = Character.toUpperCase(nameChars[0]);<br /> return new String(nameChars);<br /> }<br />}</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE for ability to use an annotation to specify per-property translation to JSON element names</li><li>COMPARABLE for ability to specify naming strategy translations for all properties</li><li>+1 Gson for providing some built-in naming strategies</li><li>+1 Jackson for providing a facility to specify different translations for fields versus setter methods versus getter methods</li></ul><br /><a name="TOC-Sharing-State-Across-Custom-Seriali"><h3>Sharing State Across Custom Serializers and Deserializers</h3></a><br />The same options are available to Jackson solutions as are available to Gson solutions for sharing state across custom serializers and deserializers. Following are examples using Jackson of the three suggested solutions listed in the Gson user manual. (The same solutions with Gson would be almost identical.) These examples all deserialize instances of the following data structure, with the desired outcome that the value of <code>Bar.b</code> is prefixed with the value of <code>Foo.a</code>.<pre name="code" class="java">class Foo<br />{<br /> public String a;<br /> public Bar bar;<br /> <br /> @Override public String toString()<br /> { return String.format("foo.a=%s, bar.b=%s", a, bar.b); }<br />}<br /><br />class Bar { public String b; }</pre>Note that controlling the order in which the different custom deserializers opperate is likely important when sharing state between them. These examples make that easy, as the target data structure heirarchy is deserialized starting from the parent -- Foo deserialization starts before Bar deserialization.<br /><br /><b>Store shared state in static fields</b><pre name="code" class="java">public class JacksonSharedStateInStaticFieldsDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> SimpleModule module = new SimpleModule(<br /> "SharedStaticDataDemo", Version.unknownVersion());<br /> module.addDeserializer(Foo.class, new FooDeserializer());<br /> module.addDeserializer(Bar.class, new BarDeserializer());<br /> <br /> ObjectMapper mapper = new ObjectMapper().withModule(module);<br /> <br /> // {"a":"A","foo":{"b":"B"}}<br /> String json = "{\"a\":\"A\",\"foo\":{\"b\":\"B\"}}";<br /> <br /> Foo foo = mapper.readValue(json, Foo.class);<br /> System.out.println(foo);<br /> // foo.a=A, bar.b=A.B<br /> }<br />}<br /><br />class FooDeserializer extends JsonDeserializer<Foo><br />{<br /> static Foo foo;<br /> <br /> @Override<br /> public Foo deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectCodec codec = jp.getCodec();<br /> ObjectNode node = (ObjectNode) codec.readTree(jp);<br /> Foo foo = new Foo();<br /> FooDeserializer.foo = foo;<br /> foo.a = node.get("a").getValueAsText();<br /> foo.bar = codec.treeToValue(node.get("foo"), Bar.class);<br /> return foo;<br /> }<br />}<br /><br />class BarDeserializer extends JsonDeserializer<Bar><br />{<br /> @Override<br /> public Bar deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectCodec codec = jp.getCodec();<br /> ObjectNode node = (ObjectNode) codec.readTree(jp);<br /> Bar bar = new Bar();<br /> bar.b = FooDeserializer.foo.a;<br /> bar.b += "." + node.get("b").getValueAsText();<br /> return bar;<br /> }<br />}</pre><b>Declare the serializer/deserializer as inner classes of a parent type, and use the instance fields of parent type to store shared state</b><pre name="code" class="java">public class JacksonSharedStateInParentClassDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> JacksonSharedStateInParentClassDemo demo = <br /> new JacksonSharedStateInParentClassDemo();<br /> <br /> SimpleModule module = new SimpleModule(<br /> "SharedStaticDataDemo", Version.unknownVersion());<br /> module.addDeserializer(<br /> Foo.class, demo.new FooDeserializer());<br /> module.addDeserializer(<br /> Bar.class, demo.new BarDeserializer());<br /> <br /> ObjectMapper mapper = new ObjectMapper().withModule(module);<br /> <br /> // {"a":"A","foo":{"b":"B"}}<br /> String json = "{\"a\":\"A\",\"foo\":{\"b\":\"B\"}}";<br /> <br /> Foo foo = mapper.readValue(json, Foo.class);<br /> System.out.println(foo);<br /> // foo.a=A, bar.b=A.B<br /> }<br /> <br /> final Map<String, String> sharedData = <br /> new HashMap<String, String>();<br /> <br /> class FooDeserializer extends JsonDeserializer<Foo><br /> {<br /> @Override<br /> public Foo deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectCodec codec = jp.getCodec();<br /> ObjectNode node = (ObjectNode) codec.readTree(jp);<br /> Foo foo = new Foo();<br /> foo.a = node.get("a").getValueAsText();<br /> sharedData.put("foo.a", foo.a);<br /> foo.bar = codec.treeToValue(node.get("foo"), Bar.class);<br /> return foo;<br /> }<br /> }<br /><br /> class BarDeserializer extends JsonDeserializer<Bar><br /> {<br /> @Override<br /> public Bar deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectCodec codec = jp.getCodec();<br /> ObjectNode node = (ObjectNode) codec.readTree(jp);<br /> Bar bar = new Bar();<br /> bar.b = sharedData.get("foo.a");<br /> bar.b += "." + node.get("b").getValueAsText();<br /> return bar;<br /> }<br /> }<br />}</pre><b>Use Java ThreadLocal</b><pre name="code" class="java">public class JacksonSharedStateWithThreadLocalDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> JacksonSharedStateWithThreadLocalDemo demo =<br /> new JacksonSharedStateWithThreadLocalDemo();<br /><br /> SimpleModule module = new SimpleModule(<br /> "SharedStaticDataDemo", Version.unknownVersion());<br /> module.addDeserializer(<br /> Foo.class, demo.new FooDeserializer());<br /> module.addDeserializer(<br /> Bar.class, demo.new BarDeserializer());<br /><br /> ObjectMapper mapper = new ObjectMapper().withModule(module);<br /><br /> // {"a":"A","foo":{"b":"B"}}<br /> String json = "{\"a\":\"A\",\"foo\":{\"b\":\"B\"}}";<br /><br /> Foo foo = mapper.readValue(json, Foo.class);<br /> System.out.println(foo);<br /> // foo.a=A, bar.b=A.B<br /> }<br /><br /> final ThreadLocal<Foo> threadLocal = new ThreadLocal<Foo>();<br /> <br /> class FooDeserializer extends JsonDeserializer<Foo><br /> {<br /> @Override<br /> public Foo deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectCodec codec = jp.getCodec();<br /> ObjectNode node = (ObjectNode) codec.readTree(jp);<br /> Foo foo = new Foo();<br /> foo.a = node.get("a").getValueAsText();<br /> threadLocal.set(foo);<br /> foo.bar = codec.treeToValue(node.get("foo"), Bar.class);<br /> threadLocal.remove();<br /> return foo;<br /> }<br /> }<br /><br /> class BarDeserializer extends JsonDeserializer<Bar><br /> {<br /> @Override<br /> public Bar deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectCodec codec = jp.getCodec();<br /> ObjectNode node = (ObjectNode) codec.readTree(jp);<br /> Bar bar = new Bar();<br /> Foo foo = threadLocal.get();<br /> bar.b = foo.a;<br /> bar.b += "." + node.get("b").getValueAsText();<br /> return bar;<br /> }<br /> }<br />}</pre><b>Comparison Rating:</b> COMPARABLE for available options to share data between custom serializers and deserializers<br /><br /><a name="TOC-Streaming"><h3>Streaming</h3></a><br />Gson provides API components to read and write character streams (not byte streams; unlike Jackson, which can operate on byte streams). Aside from the reasons outlined in <a target="gson_guide" href="http://sites.google.com/site/gson/streaming">the Gson Streaming documentation</a>, folks might choose to use the streaming API for performance concerns. According to the latest results at <a href="_blank" href="https://github.com/eishay/jvm-serializers/wiki">https://github.com/eishay/jvm-serializers/wiki</a>, Gson databinding is more than 6x slower than Gson stream processing. This performance increase comes at the cost of coding effort. <a target="_blank" href="https://github.com/eishay/jvm-serializers/blob/0e1bb3187b783becabdba37a8d88b8719613cc55/tpc/src/serializers/GsonManual.java">The Gson manual stream processing solution</a> is over 300 lines long, while <a target="_blank" href="https://github.com/eishay/jvm-serializers/blob/0e1bb3187b783becabdba37a8d88b8719613cc55/tpc/src/serializers/Gson.java">the Gson databinding solution</a> is just a few lines.<br /><br />Following are the Jackson equivalent solutions of the Gson examples.<br /><br /><b>Mixed Reads Example</b><pre name="code" class="java">public class JacksonMixedReadsDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // [{"id":"1","body":"message 1"},<br /> // {"id":"2","body":"message 2"}]<br /> String json = "[{\"id\":\"1\",\"body\":\"message 1\"}," +<br /> "{\"id\":\"2\",\"body\":\"message 2\"}]";<br /><br /> List<Message> messages = readJsonStream(<br /> new ByteArrayInputStream(json.getBytes()));<br /> System.out.println(messages);<br /> // [{id=1, body=message 1}, {id=2, body=message 2}]<br /> }<br /><br /> static List<Message> readJsonStream(InputStream in)<br /> throws IOException<br /> {<br /> List<Message> messages = new ArrayList<Message>();<br /><br /> JsonParser parser = new JsonFactory().createJsonParser(in);<br /> parser.setCodec(new ObjectMapper());<br /> parser.nextValue();<br /> while (JsonToken.START_OBJECT == parser.nextValue())<br /> {<br /> messages.add(parser.readValueAs(Message.class));<br /> }<br /> parser.close();<br /> return messages;<br /> }<br />}<br /><br />class Message<br />{<br /> public String id;<br /> public String body;<br /><br /> @Override<br /> public String toString()<br /> {<br /> return String.format("{id=%s, body=%s}", id, body);<br /> }<br />}</pre><b>Mixed Writes Example</b><pre name="code" class="java">public class JacksonMixedWritesDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> List<Message> messages = new ArrayList<Message>();<br /> messages.add(new Message("1", "message 1"));<br /> messages.add(new Message("2", "message 2"));<br /> <br /> ByteArrayOutputStream out = new ByteArrayOutputStream();<br /> writeJsonStream(out, messages);<br /> System.out.println(new String(out.toByteArray(), "UTF-8"));<br /> // [{"id":"1","body":"message 1"},<br /> // {"id":"2","body":"message 2"}]<br /> }<br /> <br /> static void writeJsonStream(OutputStream out, <br /> List<Message> messages) throws IOException<br /> {<br /> JsonGenerator generator = <br /> new JsonFactory().createJsonGenerator(out);<br /> generator.setCodec(new ObjectMapper());<br /> generator.writeStartArray();<br /> for (Message message : messages)<br /> {<br /> generator.writeObject(message);<br /> }<br /> generator.writeEndArray();<br /> generator.close();<br /> }<br />}<br /><br />class Message<br />{<br /> public String id;<br /> public String body;<br /> <br /> Message(String id, String body)<br /> {<br /> this.id = id;<br /> this.body = body;<br /> }<br /> <br /> @Override<br /> public String toString()<br /> {<br /> return String.format("{id=%s, body=%s}", id, body);<br /> }<br />}</pre><b>Additional Code Notes:</b> The Jackson alternative to Gson's <code>JsonWriter.setIndent()</code> method is to either use the built-in pretty printing functionality, by calling <code>JsonGenerator.useDefaultPrettyPrinter()</code>, or to specify use of a custom <code>PrettyPrinter</code> with a call to <code>JsonGenerator.setPrettyPrinter()</code>.<br /><br /><b>Prettyprint Example</b><br /><br />Note that this example does not demonstrate any pretty printing abilities. For some reason that's just what the Gson documentation chose to title this section.<pre name="code" class="java">public class JacksonPrettyPrintDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> JsonFactory jsonFactory = new JsonFactory();<br /><br /> // [{"id":"1","body":"message 1"},<br /> // {"id":"2","body":"message 2"}]<br /> String json = "[{\"id\":\"1\",\"body\":\"message 1\"}," +<br /> "{\"id\":\"2\",\"body\":\"message 2\"}]";<br /> JsonParser parser = jsonFactory.createJsonParser(json);<br /><br /> StringWriter out = new StringWriter();<br /> JsonGenerator generator =<br /> jsonFactory.createJsonGenerator(out);<br /><br /> prettyprint(parser, generator);<br /> parser.close();<br /> generator.close();<br /> System.out.println(out.toString());<br /> // [{"id":"1","body":"message 1"},<br /> // {"id":"2","body":"message 2"}]<br /> }<br /><br /> static void prettyprint(JsonParser parser,<br /> JsonGenerator generator) throws IOException<br /> {<br /> while (parser.nextToken() != null)<br /> {<br /> JsonToken token = parser.getCurrentToken();<br /> switch (token)<br /> {<br /> case START_ARRAY:<br /> generator.writeStartArray();<br /> break;<br /> case END_ARRAY:<br /> generator.writeEndArray();<br /> break;<br /> case START_OBJECT:<br /> generator.writeStartObject();<br /> break;<br /> case END_OBJECT:<br /> generator.writeEndObject();<br /> break;<br /> case FIELD_NAME:<br /> String name = parser.getCurrentName();<br /> generator.writeFieldName(name);<br /> break;<br /> case VALUE_STRING:<br /> String s = parser.getText();<br /> generator.writeString(s);<br /> break;<br /> case VALUE_NUMBER_INT:<br /> case VALUE_NUMBER_FLOAT:<br /> String n = parser.getText();<br /> generator.writeNumber(new BigDecimal(n));<br /> break;<br /> case VALUE_TRUE:<br /> case VALUE_FALSE:<br /> boolean b = parser.getBooleanValue();<br /> generator.writeBoolean(b);<br /> break;<br /> case VALUE_NULL:<br /> generator.writeNull();<br /> }<br /> }<br /> }<br />}</pre><b>Comparison Rating:</b> COMPARABLE for ability to read and write JSON one token at a time<br /><br /><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-6.html">Continue to part 6...</a><br /><br /><h2>References And Resources:</h2><ul><li><a target="_blank" href="http://code.google.com/p/google-gson">Gson Project Home</a></li><li><a target="_blank" href="http://jackson.codehaus.org">Jackson Project Home</a></li><li><a target="_blank" href="https://groups.google.com/forum/#!forum/google-gson">Gson User Group Mailing List</a></li><li><a target="_blank" href="http://jackson-users.ning.com">Jackson JSON User Group</a></li><li><a target="_blank" href="http://code.google.com/p/google-gson/source/checkout">The Gson Source Code</a></li><li><a target="_blank" href="http://svn.codehaus.org/jackson/">The Jackson Source Code</a></li></ul>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0tag:blogger.com,1999:blog-5325426185501267599.post-13342803520034496382011-07-03T05:09:00.014-05:002011-11-25T05:04:56.270-06:00Gson v Jackson - Part 4<table border="0"><tbody><tr><td><h2>tl;dnr</h2>Use Jackson, not Gson. Use this article as a reference for basic features.<br /><br /><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html">Part 3</a> of this short series of articles ended with section 5.10.1 of the Gson user guide, "InstanceCreator for a Parameterized Type". This fourth part continues with section 5.11 on "Compact Vs. Pretty Printing for JSON Output Format", and ends with section 5.14.3 on "User Defined Exclusion Strategies". Part 5 will continue with section 5.15 on "JSON Field Naming Support".</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/RpFIE" alt="QR Code Link To This Article" border="0" />http://goo.gl/RpFIE</div></td></tr></tbody></table><a name='more'></a><br />See <a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-6.html">part 6</a> of this series for a complete listing of and links to the various sections of this Gson user guide review.<br /><br />For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at <a href="https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip">https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip</a>.)<br /><br />This information is based on Gson release 1.7.1 and Jackson release 1.8.2.<br /><br /><h2>The Gson User Guide Walk-through Continued...</h2><br /><a name="TOC-Compact-Vs.-Pretty-Printing-for-JSO"><h3>Compact Vs. Pretty Printing for JSON Output Format</h3></a><br />Gson and Jackson have similar built-in features to pretty-print JSON.<pre name="code" class="java">// input: {"a":"A","b":{"c":"C","d":["D","E","F"]}}<br />String json = "{\"a\":\"A\",\"b\":{\"c\":\"C\",\"d\":" +<br /> "[\"D\",\"E\",\"F\"]}}";<br /><br />Gson gson = new Gson();<br />System.out.println(<br /> gson.toJson(gson.fromJson(json, Foo.class)));<br />// output: {"a":"A","b":{"c":"C","d":["D","E","F"]}}<br /><br />gson = new GsonBuilder().setPrettyPrinting().create();<br />System.out.println(<br /> gson.toJson(gson.fromJson(json, Foo.class)));<br />// output:<br />// {<br />// "a": "A",<br />// "b": {<br />// "c": "C",<br />// "d": [<br />// "D",<br />// "E",<br />// "F"<br />// ]<br />// }<br />// }<br /><br />ObjectMapper mapper = new ObjectMapper();<br />System.out.println(mapper.writeValueAsString(<br /> mapper.readValue(json, Foo.class)));<br />// output: {"a":"A","b":{"c":"C","d":["D","E","F"]}}<br />ObjectWriter prettyWriter = <br /> mapper.defaultPrettyPrintingWriter();<br />System.out.println(prettyWriter.writeValueAsString(<br /> mapper.readValue(json, Foo.class)));<br />// output:<br />// {<br />// "a" : "A",<br />// "b" : {<br />// "c" : "C",<br />// "d" : [ "D", "E", "F" ]<br />// }<br />//}</pre>Gson, however, for some reason did not expose the pretty printing feature to allow for user customizing. Jackson did.<br /><br /><b>Comparison Ratings:</b><ul><li>COMPARABLE for built-in pretty printing capability</li><li>+1 Jackson for allowing easy plug-in of custom pretty printing</li></ul><br /><a name="TOC-Null-Object-Support"><h3>Null Object Support</h3></a><br />(Some additional and overlapping information on null handling was covered in the "Finer Points with Objects" section of the Gson user guide, reviewed in <a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html">the first part of this series</a>.)<br /><br />When generating JSON, by default Gson skips inclusion of elements for which the corresponding Java field is a null reference. By default, Jackson includes them. Gson has a setting to include them. Jackson has a setting to exclude them.<pre name="code" class="java">class Foo<br />{<br /> public String a;<br /> public String b;<br />}<br /><br />Gson gson = new Gson();<br />ObjectMapper mapper = new ObjectMapper();<br /><br />// Default null serializing behavior...<br /><br />Foo foo = null;<br /><br />System.out.println(gson.toJson(foo));<br />// output: NOTHING -- EMPTY STRING<br />System.out.println("".equals(gson.toJson(foo))); // true<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: null<br /><br />foo = new Foo();<br /><br />System.out.println(gson.toJson(foo));<br />// output: {}<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: {"a":null,"b":null}<br /><br />foo.a = "42";<br /><br />System.out.println(gson.toJson(foo));<br />// output: {"a":"42"}<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: {"a":"42","b":null}<br /><br />System.out.println();<br /><br />// Reversing behaviors...<br /><br />gson = new GsonBuilder().serializeNulls().create();<br />mapper = new ObjectMapper();<br />mapper.getSerializationConfig()<br /> .setSerializationInclusion(NON_NULL);<br /><br />foo = null;<br /><br />System.out.println(gson.toJson(foo));<br />// output: null<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: null<br /><br />foo = new Foo();<br /><br />System.out.println(gson.toJson(foo));<br />// output: {"a":null,"b":null}<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: {}<br /><br />foo.a = "42";<br /><br />System.out.println(gson.toJson(foo));<br />// output: {"a":"42","b":null}<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: {"a":"42"}</pre><b>Additional Code Notes:</b> While there is a difference between how Gson and Jackson deserialize a <i>direct</i> null reference, I'm not a fan of either approach. Both generate JSON that is invalid. Valid JSON must start with either '{' or '['. I would rather see "{}" as the output of deserializing a null reference directly.<br /><br />For serializing nulls, Jackson offers configurability beyond the global on/off configuration so far demonstrated. The <code>@JsonSerialize</code> annotation can be applied to individual class definitions, or even individual fields to turn null serialization on/off with greater granularity. Also, null serialization can be further controlled by specifying a null value serializer, as demonstrated at <a target="_blank" href="http://wiki.fasterxml.com/JacksonHowToCustomSerializers">http://wiki.fasterxml.com/JacksonHowToCustomSerializers</a><br /><br /><b>Comparison Ratings:</b><ul><li>COMPARABLE for global on/off configurability of whether to serialize null references</li><li>+1 Jackson for greater configurability of null reference serialization</li></ul><br /><a name="TOC-Versioning-Support"><h3>Versioning Support</h3></a><br />Jackson currently has no built-in feature similar to Gson's versioning support, though the next release reportedly will have something comparable.<br /><br /><b>Comparison Rating:</b> +1 Gson for built-in versioning support<br /><br /><a name="TOC-Java-Modifier-Exclusion"><h3>Excluding Fields From Serialization and Deserialization - Java Modifier Exclusion</h3></a><br />Gson and Jackson offer similar mechanisms to globally specify exclusion of fields by modifier. (Note the examples in the Gson user guide don't actually compile, as the method name is "excludeFieldsWithModifiers" not "excludeFieldsWithModifier". Also, while it says, "you can use any number of the Modifier constants," only seven of the constants are actually applicable, and there is no constant for the "default" modifier -- that is, no modifier.)<br /><br /><b>A demo of Gson's ability to exclude by modifiers:</b><pre name="code" class="java">public class GsonExclusionByModifierExamples<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> Foo foo = new Foo();<br /><br /> Gson gson = new Gson();<br /> System.out.println(gson.toJson(foo));<br /> // output: {"a":"final","b":"private","c":"protected",<br /> // "d":"public","g":"volatile","h":"default"}<br /><br /> gson = gsonWithModifierIncluded(Modifier.FINAL);<br /> System.out.println(gson.toJson(foo)); <br /> // output: {"a":"final","h":"default"}<br /> gson = gsonWithModifierIncluded(Modifier.PRIVATE);<br /> System.out.println(gson.toJson(foo));<br /> // output: {"b":"private","h":"default"}<br /> gson = gsonWithModifierIncluded(Modifier.PROTECTED);<br /> System.out.println(gson.toJson(foo));<br /> // output: {"c":"protected","h":"default"}<br /> gson = gsonWithModifierIncluded(Modifier.PUBLIC);<br /> System.out.println(gson.toJson(foo));<br /> // output: {"d":"public","h":"default"}<br /> gson = gsonWithModifierIncluded(Modifier.STATIC);<br /> System.out.println(gson.toJson(foo));<br /> // output: {"e":"static","h":"default"}<br /> gson = gsonWithModifierIncluded(Modifier.TRANSIENT);<br /> System.out.println(gson.toJson(foo));<br /> // output: {"f":"transient","h":"default"}<br /> gson = gsonWithModifierIncluded(Modifier.VOLATILE);<br /> System.out.println(gson.toJson(foo));<br /> // output: {"g":"volatile","h":"default"}<br /> }<br /><br /> static Gson gsonWithModifierIncluded(int modifierToInclude)<br /> {<br /> List<Integer> modifiers = new ArrayList<Integer>();<br /> modifiers.add(Modifier.FINAL);<br /> modifiers.add(Modifier.PRIVATE);<br /> modifiers.add(Modifier.PROTECTED);<br /> modifiers.add(Modifier.PUBLIC);<br /> modifiers.add(Modifier.STATIC);<br /> modifiers.add(Modifier.TRANSIENT);<br /> modifiers.add(Modifier.VOLATILE);<br /> modifiers.remove(new Integer(modifierToInclude));<br /> return new GsonBuilder()<br /> .excludeFieldsWithModifiers(asIntArray(modifiers))<br /> .create();<br /> }<br /><br /> static int[] asIntArray(List<Integer> numbers)<br /> {<br /> int size = numbers.size();<br /> int[] ints = new int[size];<br /> for (int i = 0; i < size; i++)<br /> {<br /> ints[i] = numbers.get(i);<br /> }<br /> return ints;<br /> }<br />}<br /><br />class Foo<br />{<br /> final String a = "final";<br /> private String b = "private";<br /> protected String c = "protected";<br /> public String d = "public";<br /> static String e = "static";<br /> transient String f = "transient";<br /> volatile String g = "volatile";<br /> String h = "default";<br />}</pre>Jackson's approach is somewhat different. Instead of offering a configuration feature to exclude fields by specified modifiers, it offers a feature to include fields by visibility, and there is no similar built-in feature to configure field inclusion by modifiers static, transient, or volatile. Doing so would require custom serialization/deserialization processing.<br /><br /><b>A demo of Jackson's ability to include fields by visibility:</b><pre name="code" class="java">public class JacksonIncludeFieldsByModifierExamples<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> Bar bar = new Bar();<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {"a":"final","d":"public","g":"volatile"}<br /><br /> mapper = withModifierIncluded(Visibility.ANY);<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {"a":"final","b":"private","c":"protected",<br /> // "d":"public","g":"volatile","h":"default"}<br /> mapper = withModifierIncluded(Visibility.DEFAULT);<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {"a":"final","d":"public","g":"volatile"}<br /> mapper = withModifierIncluded(Visibility.NON_PRIVATE);<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {"a":"final","c":"protected","d":"public",<br /> // "g":"volatile","h":"default"}<br /><br /> mapper = withModifierIncluded(Visibility.NONE);<br /> mapper.configure(FAIL_ON_EMPTY_BEANS, false);<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {}<br /> // Visibility.NONE would exclude everything.<br /> // Cannot be used unless exposing some fields through<br /> // setters/getters, or disabling FAIL_ON_EMPTY_BEANS.<br /><br /> mapper = <br /> withModifierIncluded(Visibility.PROTECTED_AND_PUBLIC);<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {"a":"final","c":"protected",<br /> // "d":"public","g":"volatile"}<br /> mapper = withModifierIncluded(Visibility.PUBLIC_ONLY);<br /> System.out.println(mapper.writeValueAsString(bar));<br /> // output: {"a":"final","d":"public","g":"volatile"}<br /> }<br /><br /> static ObjectMapper <br /> withModifierIncluded(Visibility visibility)<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> VisibilityChecker<?> visibilityChecker =<br /> mapper.getVisibilityChecker()<br /> .withFieldVisibility(visibility);<br /> mapper.setVisibilityChecker(visibilityChecker);<br /> return mapper;<br /> }<br />}<br /><br />class Bar<br />{<br /> public final String a = "final";<br /> private String b = "private";<br /> protected String c = "protected";<br /> public String d = "public";<br /> public static String e = "static";<br /> public transient String f = "transient";<br /> public volatile String g = "volatile";<br /> String h = "default";<br />}</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE for global configurability to include fields by modifier</li><li>+1 Gson for ability to configure field inclusion by modifiers static, transient, or volatile</li><li>+1 Jackson for ability to configure field inclusion by default visibility</li></ul><br /><a name="TOC-Gson-s-Expose"><h3>Excluding Fields From Serialization and Deserialization - Gson's @Expose</h3></a><br />Creating a <code>Gson</code> instance with <code>new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()</code> allows exclusion of all fields from participating in serialization or deserialization, except those marked with the <code>@Expose</code> annotation. This is arguably convenient if a Java structure has a lot of fields, only few of which are wanted for serializing or deserializing. However, this configuration mechanism is inconvenient, if alternatively a Java structure with a lot of fields only has a few that are not wanted for serializing or deserializing. (Note that the <code>@Expose</code> annotation can also optionally be specified for just serialization or for just deserialization.)<br /><br /><b>A demo of Gson's ability to only include fields marked with <code>@Expose</code>:</b><pre name="code" class="java">public class GsonExposeFieldsDemo<br />{<br /> public static void main(String[] args)<br /> {<br /> Gson gson = new GsonBuilder()<br /> .excludeFieldsWithoutExposeAnnotation().create();<br /><br /> String json1 = gson.toJson(new Foo());<br /> System.out.println(json1);<br /> // output: {"b":"B"}<br /> // only b was exposed during serialization<br /><br /> // input: {"a":"xyz","b":"42"}<br /> String json2 = "{\"a\":\"xyz\",\"b\":\"42\"}";<br /> Foo foo = gson.fromJson(json2, Foo.class);<br /> System.out.println(foo.a); // output: A<br /> System.out.println(foo.b); // output: 42<br /> // only b was exposed during deserialization<br /> }<br />}<br /><br />class Foo<br />{<br /> public String a = "A";<br /> @Expose<br /> public String b = "B";<br />}</pre>Jackson provides the opposite annotation-based configurability: it allows inclusion of all fields from participating in serialization or deserialization, except those marked with the <code>@JsonIgnore</code> annotation. This is arguably convenient if a Java structure has a lot of fields, only few of which are not wanted for serializing or deserializing. However, this configuration mechanism is inconvenient, if alternatively a Java structure with a lot of fields only has a few that are wanted for serializing or deserializing.<br /><br /><b>A demo of Jackson's ability to exclude fields marked with <code>@JsonIgnore</code>:</b><pre name="code" class="java">public class JacksonIgnoreFieldsDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /><br /> String json1 = mapper.writeValueAsString(new Bar());<br /> System.out.println(json1);<br /> // output: {"a":"A"}<br /> // b was ignored during serialization<br /><br /> // input: {"a":"xyz","b":"42"}<br /> String json2 = "{\"a\":\"xyz\",\"b\":\"42\"}";<br /> Bar bar = mapper.readValue(json2, Bar.class);<br /> System.out.println(bar.a); // xyz<br /> System.out.println(bar.b); // B<br /> // b was ignored during deserialization<br /> }<br />}<br /><br />class Bar<br />{<br /> public String a = "A";<br /> @JsonIgnore<br /> public String b = "B";<br />}</pre>Gson does not currently have a built-in feature comparable to Jackson's <code>@JsonIgnore</code> annotation (though the next section of the Gson user guide demonstrates an easy way to add this functionality with a custom <code>ExclusionStrategy</code>). To implement functionality comparable to Gson's <code>@Expose</code> feature, Jackson provides a few options.<ul><li>The Jackson feature that most closely resembles the combination of Gson's <code>excludeFieldsWithoutExposeAnnotation()</code> & <code>@Expose</code> configurations is the <code>ObjectMapper.setVisibilityChecker()</code> & <code>@JsonProperty</code> combination. Instead of using <code>setVisibilityChecker()</code> to change visibility access globally, <code>@JsonAutoDetect</code> can be employed to configure visibility access per class.</li><li>For serialization only, Jackson provides custom <a target="_blank" href="http://wiki.fasterxml.com/JacksonJsonViews">Views</a> configurations, which can be used to mimic Gson's <code>@Expose</code>.</li><li>Custom processing to exclude all fields except those marked with a particular annotation can also be implemented using a custom <code>AnnotationIntrospector</code> (demonstrated in the next section).</li></ul>Following is an example of using the <code>ObjectMapper.setVisibilityChecker()</code> & <code>@JsonProperty</code> combination. (Note: This approach will be made slightly simpler once <a target="_blank" href="http://jira.codehaus.org/browse/JACKSON-621">issue 621</a> is implemented.)<pre name="code" class="java">public class JacksonExposeFieldsDemo1<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);<br /> mapper.setVisibilityChecker(<br /> mapper.getVisibilityChecker()<br /> .withCreatorVisibility(Visibility.NONE)<br /> .withFieldVisibility(Visibility.NONE)<br /> .withGetterVisibility(Visibility.NONE)<br /> .withIsGetterVisibility(Visibility.NONE)<br /> .withSetterVisibility(Visibility.NONE));<br /><br /> String json1 = mapper.writeValueAsString(new Foo());<br /> System.out.println(json1);<br /> // output: {"b":"B"}<br /> // only b was exposed during serialization<br /><br /> // input: {"a":"xyz","b":"42"}<br /> String json2 = "{\"a\":\"xyz\",\"b\":\"42\"}";<br /> Foo foo = mapper.readValue(json2, Foo.class);<br /> System.out.println(foo.a); // output: A<br /> System.out.println(foo.b); // output: 42<br /> // only b was exposed during deserialization<br /> }<br />}<br /><br />class Foo<br />{<br /> public String a = "A";<br /> @JsonProperty<br /> public String b = "B";<br />}</pre>Following is a similar approach using <code>@JsonAutoDetect</code> instead of configuring the <code>ObjectMapper</code>'s <code>VisibilityChecker</code>.<pre name="code" class="java">public class JacksonExposeFieldsDemo2<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);<br /><br /> String json1 = mapper.writeValueAsString(new Foo());<br /> System.out.println(json1);<br /> // output: {"b":"B"}<br /> // only b was exposed during serialization<br /><br /> // input: {"a":"xyz","b":"42"}<br /> String json2 = "{\"a\":\"xyz\",\"b\":\"42\"}";<br /> Foo foo = mapper.readValue(json2, Foo.class);<br /> System.out.println(foo.a); // output: A<br /> System.out.println(foo.b); // output: 42<br /> // only b was exposed during deserialization<br /> }<br />}<br /><br />@JsonAutoDetect(<br /> creatorVisibility=Visibility.NONE,<br /> fieldVisibility=Visibility.NONE,<br /> getterVisibility=Visibility.NONE,<br /> isGetterVisibility=Visibility.NONE,<br /> setterVisibility=Visibility.NONE)<br />class Foo<br />{<br /> public String a = "A";<br /> @JsonProperty<br /> public String b = "B";<br />}</pre>Following is an example using a custom <a target="_blank" href="http://wiki.fasterxml.com/JacksonJsonViews">Views</a> configuration. Again, note this approach applies to serialization only.<pre name="code" class="java">public class JacksonExposeFieldsForSerializationDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectWriter writer = new ObjectMapper()<br /> .configure(DEFAULT_VIEW_INCLUSION, false)<br /> .viewWriter(Expose.class);<br /> <br /> String json1 = writer.writeValueAsString(new Foo());<br /> System.out.println(json1);<br /> // output: {"b":"B"}<br /> <br /> // Or, using ObjectMapper instead of ObjectWriter<br /> ObjectMapper mapper = new ObjectMapper()<br /> .configure(DEFAULT_VIEW_INCLUSION, false);<br /> mapper.setSerializationConfig(<br /> mapper.getSerializationConfig().withView(Expose.class));<br /> <br /> String json2 = mapper.writeValueAsString(new Foo());<br /> System.out.println(json2);<br /> // output: {"b":"B"}<br /> }<br />}<br /><br />class Foo<br />{<br /> public String a = "A";<br /> @JsonView(Expose.class) public String b = "B";<br />}<br /><br />// Used only as JsonView marker.<br />// Could use any existing class, like Object, instead.<br />class Expose {}</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE for simple configurability to include only specified fields for participation in serialization/deserialization</li><li>+1 Jackson for simple configurability to exclude only specified fields for participation in serialization/deserialization (the Gson equivalent, demonstrated below, is almost as simple)</li><li>+1 Jackson for greater configurability options to include only specified fields for participation in serialization/deserialization (including global/class/field/getter/setter configuration options)</li></ul><br /><a name="TOC-User-Defined-Exclusion-Strategies"><h3>Excluding Fields From Serialization and Deserialization - User Defined Exclusion Strategies</h3></a><br />Again, the Gson user guide provides us with an example that does not compile, along with incorrect example output, if the most obvious compiler error fixes were applied.<br /><br />Following is a working example of Gson's custom <code>ExclusionStrategy</code> facility.<pre name="code" class="java">public class GsonExclusionStrategyDemo<br />{<br /> public static void main(String[] args)<br /> {<br /> SampleObjectForTest src = new SampleObjectForTest();<br /><br /> Gson gson = new Gson();<br /> System.out.println(gson.toJson(src));<br /> // {"annotatedField":5,"stringField":"someDefaultValue",<br /> // "longField":1234}<br /><br /> gson = new GsonBuilder().setExclusionStrategies(<br /> new MyExclusionStrategy(String.class)).create();<br /> System.out.println(gson.toJson(src));<br /> // {"longField":1234}<br /><br /> // {"annotatedField":42,"stringField":"a string value",<br /> // "longField":9876}<br /> String json = "{\"annotatedField\":42," +<br /> "\"stringField\":\"a string value\",\"longField\":9876}";<br /><br /> SampleObjectForTest srcCopy =<br /> gson.fromJson(json, SampleObjectForTest.class);<br /> System.out.println(srcCopy.annotatedField);<br /> // output: 5 -- Field skipped during deserialization.<br /> System.out.println(srcCopy.stringField);<br /> // output: someDefaultValue -- Field skipped.<br /> System.out.println(srcCopy.longField);<br /> // output: 9876<br /> }<br />}<br /><br />@Retention(RetentionPolicy.RUNTIME)<br />@Target({ ElementType.FIELD })<br />@interface Foo {}<br /><br />class SampleObjectForTest<br />{<br /> @Foo<br /> public final int annotatedField;<br /> public final String stringField;<br /> public final long longField;<br /><br /> public SampleObjectForTest()<br /> {<br /> annotatedField = 5;<br /> stringField = "someDefaultValue";<br /> longField = 1234;<br /> }<br />}<br /><br />class MyExclusionStrategy implements ExclusionStrategy<br />{<br /> private final Class<?> typeToSkip;<br /><br /> public MyExclusionStrategy(Class<?> typeToSkip)<br /> {<br /> this.typeToSkip = typeToSkip;<br /> }<br /><br /> @Override<br /> public boolean shouldSkipClass(Class<?> clazz)<br /> {<br /> return (clazz == typeToSkip);<br /> }<br /><br /> @Override<br /> public boolean shouldSkipField(FieldAttributes f)<br /> {<br /> return f.getAnnotation(Foo.class) != null;<br /> }<br />}</pre>This example demonstrates two uses of custom exclusion strategies: 1. to exclude all fields of a particular type; and 2. to exclude all fields marked with a specified annotation. The comparable Jackson implementations of these two specific functionalities are: 1. the <code>@JsonIgnoreType</code> annotation; and 2. the <code>@JsonIgnore</code> annotation, briefly reviewed in the previous section.<br /><br />Following is a Jackson solution that provides the same functionality as in the previous Gson demo to skip fields that are either marked with a particular annotation or of a particular type (<code>java.lang.String</code> in this case).<pre name="code" class="java">public class JacksonExcludeTypesAndPropertiesDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> System.out.println(mapper.writeValueAsString(new Bar()));<br /> // {"stringField":"someDefaultValue","longField":1234}<br /><br /> mapper = new ObjectMapper();<br /> mapper.getSerializationConfig().addMixInAnnotations(<br /> String.class, IgnoreStringsMixIn.class);<br /> mapper.getDeserializationConfig().addMixInAnnotations(<br /> String.class, IgnoreStringsMixIn.class);<br /> System.out.println(mapper.writeValueAsString(new Bar()));<br /> // {"longField":1234}<br /><br /> // {"annotatedField":42,"stringField":"a string value",<br /> // "longField":9876}<br /> String json = "{\"annotatedField\":42," +<br /> "\"stringField\":\"a string value\",\"longField\":9876}";<br /><br /> Bar bar = mapper.readValue(json, Bar.class);<br /> System.out.println(bar.annotatedField);<br /> // output: 5 -- Field skipped during deserialization.<br /> System.out.println(bar.stringField);<br /> // output: someDefaultValue -- Field skipped.<br /> System.out.println(bar.longField);<br /> // output: 9876<br /> }<br />}<br /><br />@JsonIgnoreType<br />abstract class IgnoreStringsMixIn {}<br /><br />class Bar<br />{<br /> @JsonIgnore<br /> public final int annotatedField;<br /> public final String stringField;<br /> public final long longField;<br /><br /> public Bar()<br /> {<br /> annotatedField = 5;<br /> stringField = "someDefaultValue";<br /> longField = 1234;<br /> }<br />}</pre>This last example of course did not demonstrate a comparable Jackson solution to Gson's custom exclusion strategies. Gson's custom exclusion strategies provide the ability to exclude fields for conditions other than annotations or types. Any combination of factors including the field name, the applied annotations, the declared class, the declared type, and the declaring class can be considered. With Jackson, to exclude fields simply by name, the built-in feature is the <code>@JsonIgnoreProperties</code> annotation, to exclude fields simply by type is the <code>@JsonIgnoreType</code> annotation (as previously demonstrated), and to exclude fields simply by the declaring class is also the <code>@JsonIgnoreType</code> annotation. To equivalently combine arbitrary exclusion logic as the Gson exclusion strategy feature allows, a few different approaches with Jackson are available, partially including custom contextual serializers/deserializers, and/or (as "Unknown" commented below) custom <a target="_blank" href="http://wiki.fasterxml.com/JacksonJsonViews">Views</a>, and/or custom <a target="_blank" href="http://wiki.fasterxml.com/JacksonFeatureJsonFilter">Filters</a>, and/or custom <code>BeanSerializer</code> and <code>BeanDeserializer</code> processing. But the simplest approach I've arrived at uses instead a custom <code>AnnotationIntrospector</code>, with which it's possible to implement exclusion logic based on any combination of the field name, the applied annotations, the parameterized generic type, the raw type, the declaring class, and the field modifiers, e.g., public, final. The following Jackson example closely replicates a Gson exclusion strategy.<pre name="code" class="java">class SampleObjectForTest<br />{<br /> @Foo<br /> public final int annotatedField;<br /> public final String stringField;<br /> public final long longField;<br /><br /> public SampleObjectForTest()<br /> {<br /> annotatedField = 5;<br /> stringField = "someDefaultValue";<br /> longField = 1234;<br /> }<br />}<br /><br />public class JacksonExclusionStrategyDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> SampleObjectForTest src = new SampleObjectForTest();<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /><br /> System.out.println(mapper.writeValueAsString(src));<br /> // output: {"annotatedField":5,<br /> // "stringField":"someDefaultValue","longField":1234}<br /><br /> Class[] ignorableClasses = { String.class };<br /> Class[] ignorableAnnotations = { Foo.class };<br /><br /> mapper = new ObjectMapper();<br /> mapper.setAnnotationIntrospector(<br /> new ExclusionAnnotationIntrospector(<br /> ignorableClasses, ignorableAnnotations));<br /><br /> System.out.println(mapper.writeValueAsString(src));<br /> // output: {"longField":1234}<br /> <br /> // {"annotatedField":42,"stringField":"a string value",<br /> // "longField":9876}<br /> String json = "{\"annotatedField\":42," +<br /> "\"stringField\":\"a string value\",\"longField\":9876}";<br /> <br /> SampleObjectForTest srcCopy =<br /> mapper.readValue(json, SampleObjectForTest.class);<br /> System.out.println(srcCopy.annotatedField);<br /> // output: 5 -- Field skipped during deserialization.<br /> System.out.println(srcCopy.stringField);<br /> // output: someDefaultValue -- Field skipped.<br /> System.out.println(srcCopy.longField);<br /> // output: 9876<br /> }<br />}<br /><br />@Retention(RetentionPolicy.RUNTIME)<br />@Target({ ElementType.FIELD })<br />@interface Foo {}<br /><br />class ExclusionAnnotationIntrospector<br /> extends JacksonAnnotationIntrospector<br />{<br /> Class[] ignorableClasses;<br /> Class[] ignorableAnnotations;<br /><br /> ExclusionAnnotationIntrospector(<br /> Class[] ignorableClasses, Class[] ignorableAnnotations)<br /> {<br /> this.ignorableClasses = ignorableClasses;<br /> this.ignorableAnnotations = ignorableAnnotations;<br /> }<br /><br /> public boolean shouldSkipClass(Class<?> clazz)<br /> {<br /> for (Class ignorableClass : ignorableClasses)<br /> {<br /> if (ignorableClass.equals(clazz))<br /> return true;<br /> }<br /> return false;<br /> }<br /><br /> public boolean shouldSkipField(AnnotatedField field)<br /> {<br /> for (Class ignorableAnnotation : ignorableAnnotations)<br /> {<br /> if (field.hasAnnotation(ignorableAnnotation))<br /> return true;<br /> }<br /> return false;<br /> }<br /> <br /> @Override<br /> public boolean isIgnorableField(AnnotatedField field)<br /> {<br /> if (shouldSkipClass(field.getRawType()) || <br /> shouldSkipField(field))<br /> return true;<br /> return super.isIgnorableField(field);<br /> }<br /><br /> @Override<br /> public boolean isHandled(Annotation ann)<br /> {<br /> Class clazz = ann.annotationType();<br /> for (Class ignorableAnnotation : ignorableAnnotations)<br /> if (ignorableAnnotation.equals(clazz))<br /> return true;<br /> return super.isHandled(ann);<br /> }<br />}</pre>With Jackson, another approach to filter properties during serialization is to make use of a <a target="_blank" href="http://wiki.fasterxml.com/JacksonFeatureJsonFilter">FilterProvider</a>. The following demonstrates using a FilterProvider to exclude all properties named "id" or "color" during serialization.<pre name="code" class="java">@JsonFilter("filter properties by name")<br />class PropertyFilterMixIn {}<br /><br />class Bar<br />{<br /> public String id = "42";<br /> public String name = "Fred";<br /> public String color = "blue";<br /> public Foo foo = new Foo();<br />}<br /><br />class Foo<br />{<br /> public String id = "99";<br /> public String size = "big";<br /> public String height = "tall";<br />}<br /><br />public class JacksonFoo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.getSerializationConfig().addMixInAnnotations(<br /> Object.class, PropertyFilterMixIn.class);<br /><br /> String[] ignorableFieldNames = { "id", "color" };<br /> FilterProvider filters = new SimpleFilterProvider()<br /> .addFilter("filter properties by name", <br /> SimpleBeanPropertyFilter.serializeAllExcept(<br /> ignorableFieldNames));<br /> ObjectWriter writer = mapper.writer(filters);<br /><br /> System.out.println(writer.writeValueAsString(new Bar()));<br /> // output:<br /> // {"name":"James","foo":{"size":"big","height":"tall"}}<br /> }<br />}</pre>(I logged <a target="_blank" href="http://jira.codehaus.org/browse/JACKSON-724">Jackson issue 724</a> to reduce the configuration code from this last example by a few lines. Don't hesitate to vote for its implementation.)<br /><br /><b>Comparison Ratings:</b> COMPARABLE for simple configurability of arbitrary logic to exclude fields during serialization and deserialization<br /><br /><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-5.html">Continue to part 5...</a><br /><br /><h2>References And Resources:</h2><ul><li><a target="_blank" href="http://code.google.com/p/google-gson">Gson Project Home</a></li><li><a target="_blank" href="http://jackson.codehaus.org">Jackson Project Home</a></li><li><a target="_blank" href="https://groups.google.com/forum/#!forum/google-gson">Gson User Group Mailing List</a></li><li><a target="_blank" href="http://jackson-users.ning.com">Jackson JSON User Group</a></li><li><a target="_blank" href="http://code.google.com/p/google-gson/source/checkout">The Gson Source Code</a></li><li><a target="_blank" href="http://svn.codehaus.org/jackson/">The Jackson Source Code</a></li></ul>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com2tag:blogger.com,1999:blog-5325426185501267599.post-24709596783379739882011-07-02T22:10:00.010-05:002011-11-15T10:13:34.524-06:00Gson v Jackson - Part 3<table border="0"><tbody><tr><td><h2>tl;dnr</h2>Use Jackson, not Gson. Use this article as a reference for basic features.<br /><br /><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html">Part 2</a> of this short series of articles ended with section 5.7 of the Gson user guide, titled "Serializing and Deserializing Collection with Objects of Arbitrary Types". This third part continues with section 5.8 on "Built-in Serializers and Deserializers", and ends with section 5.10.1 on "InstanceCreator for a Parameterized Type". Part 4 will continue with section 5.11 on "Compact Vs. Pretty Printing for JSON Output Format".</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/HsIea" alt="QR Code Link To This Article" border="0" />http://goo.gl/HsIea</div></td></tr></tbody></table><a name='more'></a><br />See <a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-6.html">part 6</a> of this series for a complete listing of and links to the various sections of this Gson user guide review.<br /><br />For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at <a href="https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip">https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip</a>.)<br /><br />This information is based on Gson release 1.7.1 and Jackson release 1.8.2.<br /><br /><h2>The Gson User Guide Walk-through Continued...</h2><br /><a name="TOC-Built-in-Serializers-and-Deserializ"><h3>Built-in Serializers and Deserializers</h3></a><br />Both Gson and Jackson have built-in support to serialize and deserialize java.net.URI and java.net.URL instances.<pre name="code" class="java">String urlJson = "\"http://code.google.com/p/google-gson/\"";<br />String uriJson = "\"/p/google-gson/\"";<br /><br />Gson gson = new Gson();<br /><br />URL url1 = gson.fromJson(urlJson, URL.class);<br />System.out.println(gson.toJson(url1));<br />// output: "http://code.google.com/p/google-gson/"<br /><br />URI uri1 = gson.fromJson(uriJson, URI.class);<br />System.out.println(gson.toJson(uri1));<br />// output: "/p/google-gson/"<br /><br />ObjectMapper mapper = new ObjectMapper();<br /><br />URL url2 = mapper.readValue(urlJson, URL.class);<br />System.out.println(mapper.writeValueAsString(url2));<br />// output: "http://code.google.com/p/google-gson/"<br /><br />URI uri2 = mapper.readValue(uriJson, URI.class);<br />System.out.println(mapper.writeValueAsString(uri2));<br />// output: "/p/google-gson/"</pre>Jackson also has built-in support for <a target="_blank" href="http://joda-time.sourceforge.net/">Joda Time</a>, java.util.TimeZone, java.net.InetAddress, and java.sql.Timestamp objects.<br /><br /><b>Comparison Rating:</b> +1 Jackson for more built-in serializers and deserializers<br /><br /><a name="TOC-Custom-Serialization-and-Deserializ"><h3>Custom Serialization and Deserialization</h3></a><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Custom-Serialization-and-Deserializ">relevant section in the Gson user guide</a>.<br /><br /><b>The comparable Jackson Code (assuming the built-in Joda Time handling were not present):</b><pre name="code" class="java">public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> DateTimeDeserializer deserializer = <br /> new DateTimeDeserializer();<br /> DateTimeSerializer serializer = new DateTimeSerializer();<br /> SimpleModule module =<br /> new SimpleModule("DateTimeModule",<br /> new Version(1, 0, 0, null));<br /> module.addDeserializer(DateTime.class, deserializer);<br /> module.addSerializer(DateTime.class, serializer);<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.registerModule(module);<br /> <br /> DateTime dateTime = new DateTime();<br /> System.out.println(dateTime); <br /> // output: 2011-06-27T21:34:06.046-07:00<br /> <br /> String dateTimeJson1 = <br /> mapper.writeValueAsString(dateTime);<br /> System.out.println(dateTimeJson1);<br /> // output: "2011-06-27T21:41:57.479-07:00"<br /> <br /> DateTime dateTimeCopy1 = <br /> mapper.readValue(dateTimeJson1, DateTime.class);<br /> System.out.println(dateTimeCopy1);<br /> // output: 2011-06-27T21:41:57.479-07:00<br /> }<br />}<br /><br />class DateTimeDeserializer extends StdDeserializer<DateTime><br />{<br /> DateTimeDeserializer()<br /> {<br /> super(DateTime.class);<br /> }<br /><br /> @Override<br /> public DateTime deserialize(JsonParser jp, <br /> DeserializationContext ctxt) <br /> throws IOException, JsonProcessingException<br /> {<br /> return new DateTime(jp.getText());<br /> }<br />}<br /><br />class DateTimeSerializer extends JsonSerializer<DateTime><br />{<br /> @Override<br /> public void serialize(DateTime value, JsonGenerator jgen, <br /> SerializerProvider provider) <br /> throws IOException, JsonProcessingException<br /> {<br /> jgen.writeString(value.toString());<br /> }<br />}</pre><b>Comparison Rating:</b> COMPARABLE<br /><br /><h3>Custom Serialization and Deserialization - Finer points with Serializers and Deserializers</h3><br />This section of the Gson user guide is not easy to work through, as it's prone with errors and false information.<br /><br />Some of the example code related to this section lives in the JavaDocs for <a target="_blank" href="http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/JsonSerializer.html">JsonSerializer</a> and <a target="_blank" href="http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/JsonDeserializer.html">JsonDeserializer</a>. Archived copies of which are available at <a href="https://sites.google.com/site/programmerbruce/downloads/gson-1.7.1-javadoc.jar">https://sites.google.com/site/programmerbruce/downloads/gson-1.7.1-javadoc.jar</a>.<br /><br />This code does not compile. Even if mostly-obvious changes were made so that it would compile, it still doesn't work as presented. (Please, someone make sense of this mess and prove me wrong.)<br /><br />The documentation also incorrectly describes current default Gson behavior. For example, the current default serialization of Id(java.lang.String, 42) is {"clazz":{},"value":42} not {"clazz":"java.lang.String","value":42}, though the claimed result certainly appears to be more desirable. (In <a target="_blank" href="http://code.google.com/p/google-gson/issues/detail?id=340#c2">a resent post</a>, a Gson project manager explained that the disabled Class serialization is intentional, for security reasons, though the explanation goes on to describe that the security risk is present during deserialization. Since serialization is not the same as deserialization, I don't understand how this is an explanation for the disabled serialization.)<br /><br />Some of the apparent intention of this section of the user guide is covered in the "InstanceCreator for a Parameterized Type" section.<br /><br /><a name="TOC-Writing-an-Instance-Creator"><h3>Writing an Instance Creator</h3></a><br />The latest release of Gson no longer needs an InstanceCreator to deserialize an instance of a class without a no-argument constructor. So, the MoneyInstanceCreator described in this section of the Gson user guide is unnecessary.<pre name="code" class="java">public class Foo<br />{<br /> public static void main(String[] args)<br /> {<br /> Gson gson = new Gson();<br /> <br /> Money m1 = new Money("42", CurrencyCode.MXP);<br /> String json1 = gson.toJson(m1);<br /> System.out.println(json1); <br /> // {"value":"42","currency":"MXP"}<br /> <br /> Money m1Copy = gson.fromJson(json1, Money.class);<br /> System.out.println(m1Copy.value); // 42<br /> System.out.println(m1Copy.currency.name()); // MXP<br /> }<br />}<br /><br />class Money<br />{<br /> public String value;<br /> public CurrencyCode currency;<br /> <br /> public Money(String v, CurrencyCode c)<br /> {<br /> value = v;<br /> currency = c;<br /> }<br />}<br /><br />enum CurrencyCode<br />{<br /> USD, MXP<br />}</pre>If a no-argument constructor is not present, then Jackson requires configuration information to know how to create instances of the target Java data structure. (Similar to how Gson previously required an InstanceCreator.) The configuration information is easily provided with annotations. If the target classes are not to be modified, then Jackson provides support to mix-in the annotations as follows.<pre name="code" class="java">public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> Gson gson = new Gson();<br /> <br /> Money m1 = new Money("42", CurrencyCode.MXP);<br /> String json1 = gson.toJson(m1);<br /> System.out.println(json1); <br /> // {"value":"42","currency":"MXP"}<br /> <br /> Money m1Copy = gson.fromJson(json1, Money.class);<br /> System.out.println(m1Copy.value); // 42<br /> System.out.println(m1Copy.currency.name()); // MXP<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> String json2 = mapper.writeValueAsString(m1);<br /> System.out.println(json2);<br /> // {"value":"42","currency":"MXP"}<br /> <br /> // throws JsonMappingException, complaining: <br /> // No suitable constructor found<br /> // Money m1Copy2 = mapper.readValue(json2, Money.class);<br /> <br /> mapper.getDeserializationConfig().addMixInAnnotations(<br /> Money.class, MoneyCreatorMixIn.class);<br /> Money m1Copy2 = mapper.readValue(json2, Money.class);<br /> System.out.println(m1Copy2.value); // 42<br /> System.out.println(m1Copy2.currency.name()); // MXP<br /> }<br />}<br /><br />abstract class MoneyCreatorMixIn<br />{<br /> @JsonCreator<br /> MoneyCreatorMixIn(<br /> @JsonProperty("value") String v, <br /> @JsonProperty("currency") CurrencyCode c)<br /> {<br /> <br /> }<br />}<br /><br />class Money<br />{<br /> public String value;<br /> public CurrencyCode currency;<br /> <br /> public Money(String v, CurrencyCode c)<br /> {<br /> value = v;<br /> currency = c;<br /> }<br />}<br /><br />enum CurrencyCode<br />{<br /> USD, MXP<br />}</pre><b>Comparison Ratings:</b> +1 Gson for ability to create Java instances without no-argument constructors and without explicit configuration information<br /><br /><a name="TOC-InstanceCreator-for-a-Parameterized"><h3>InstanceCreator for a Parameterized Type</h3></a><br />The following review of this section of the Gson user guide is much longer than initially expected, covering uses and details of Gson and Jackson beyond Gson's InstanceCreator feature. This occurred, since the example that Gson presented introduced deserialization concerns of greater depth than simple use of an InstanceCreator. For the casual reader interested only in what a Gson InstanceCreator is and what the specific Jackson counterpart is, here's what should be understood.<ul><li>A Gson InstanceCreator is simply used to construct an instance of an object with default values. This instance is typically later used during deserialization to replace the default values with values from the JSON. This can be useful in situations where the JSON may not contain values for all of the Java fields to be populated.</li><li>The Jackson equivalent approach is to just directly create an instance of the target object with default values (instead of indirectly through an InstanceCreator), and then use an updating reader to replace the values initially specified with values from the JSON. This approach is demonstrated in one of the examples below. (Search for "updatingReader".)</li></ul>Back to the example in the Gson user guide...<br /><br />Correcting for compiler errors and missing information, here is what I understand the example Gson code was intended to be.<pre name="code" class="java">public class GsonInstanceCreatorForParameterizedTypeDemo<br />{<br /> public static void main(String[] args)<br /> {<br /> Id<String> id1 = new Id<String>(String.class, 42);<br /><br /> Gson gson = new GsonBuilder().registerTypeAdapter(Id.class,<br /> new IdInstanceCreator()).create();<br /> String json1 = gson.toJson(id1);<br /> System.out.println(json1);<br /> // actual output: {"classOfId":{},"value":42}<br /> // This contradicts what the Gson docs say happens.<br /> // With custom serialization, as described in a<br /> // previous Gson user guide section, <br /> // intended output may be <br /> // {"value":42}<br /><br /> // input: {"value":42}<br /> String json2 = "{\"value\":42}";<br /><br /> Type idOfStringType=new TypeToken<Id<String>>(){}.getType();<br /> Id<String> id1Copy = gson.fromJson(json2, idOfStringType);<br /> System.out.println(id1Copy);<br /> // output: classOfId=class java.lang.String, value=42<br /><br /> Type idOfGsonType = new TypeToken<Id<Gson>>() {}.getType();<br /> Id<Gson> idOfGson = gson.fromJson(json2, idOfGsonType);<br /> System.out.println(idOfGson);<br /> // output: classOfId=class com.google.gson.Gson, value=42<br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br /><br /> @Override<br /> public String toString()<br /> {<br /> return "classOfId=" + classOfId + ", value=" + value;<br /> }<br />}<br /><br />class IdInstanceCreator implements InstanceCreator<Id<?>><br />{<br /> @SuppressWarnings({ "unchecked", "rawtypes" })<br /> public Id<?> createInstance(Type type)<br /> {<br /> Type[] typeParameters = <br /> ((ParameterizedType) type).getActualTypeArguments();<br /> Type idType = typeParameters[0];<br /> return new Id((Class<?>) idType, 0L);<br /> }<br />}</pre>The approaches to solve this same problem with Jackson are somewhat different. The following presents a few options available.<br /><br />This first simple approach "cheats" in two ways: 1. it requires that the JSON element "classOfId" have the correct, non-null value, so it fixes the input JSON accordingly; and 2. it disregards parameterized type information.<pre name="code" class="java">public class JacksonIdInstanceCreationDemo1<br />{<br /> @SuppressWarnings("unchecked")<br /> public static void main(String[] args) throws Exception<br /> {<br /> SimpleModule module = <br /> new SimpleModule("ClassDeserializerModule",<br /> new Version(1, 0, 0, null));<br /> module.addDeserializer(Id.class, new IdDeserializer());<br /><br /> ObjectMapper mapper = new ObjectMapper().withModule(module);<br /> mapper.getSerializationConfig()<br /> .addMixInAnnotations(Id.class, IdCreatorMixIn.class);<br /> <br /> Id<String> idOfString = new Id<String>(String.class, 42);<br /> <br /> // Serializing idOfString<br /> String idOfStringJson =<br /> mapper.writeValueAsString(idOfString);<br /> System.out.println(idOfStringJson);<br /> // output: {"classOfId":"java.lang.String","value":42}<br /> <br /> // Deserializing <br /> // {"classOfId":"java.lang.String","value":42}<br /> // to Id, without parameterized type info<br /> Id<?> idOfStringCopy1 =<br /> mapper.readValue(idOfStringJson, Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy1));<br /> // output: {"classOfId":"java.lang.String","value":42}<br /> <br /> String idOfObjectMapperJson = <br /> setClassOfId(ObjectMapper.class, idOfStringJson, mapper);<br /> <br /> // Deserializing<br /> // {"classOfId":"org.codehaus.jackson.map.ObjectMapper",<br /> // "value":42}<br /> // to Id, without parameterized type info<br /> Id<ObjectMapper> idOfObjectMapper =<br /> mapper.readValue(idOfObjectMapperJson, Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfObjectMapper));<br /> // output: {"value":42,<br /> // "classOfId":"org.codehaus.jackson.map.ObjectMapper"}<br /> <br /> // input: {"value":42}<br /> String json2 = "{\"value\":42}";<br /> <br /> // Fixing {"value":42}<br /> // to {"classOfId":"java.lang.String","value":42}<br /> // Deserializing to Id, without parameterized type info<br /> Id<String> idOfStringCopy3 = mapper.readValue(<br /> setClassOfId(String.class, json2, mapper), Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy3));<br /> // output: {"classOfId":"java.lang.String","value":42}<br /> }<br /><br /> static String setClassOfId(Class<?> value, <br /> String jsonObject, ObjectMapper mapper) throws Exception<br /> {<br /> ObjectNode node = (ObjectNode) mapper.readTree(jsonObject);<br /> node.put("classOfId", value.getName());<br /> return node.toString();<br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br />}<br /><br />// Necessary to expose private fields for serialization.<br />// Deserialization just uses public constructor.<br />@JsonAutoDetect(fieldVisibility = Visibility.ANY)<br />abstract class IdCreatorMixIn<T> {}<br /><br />class IdDeserializer extends JsonDeserializer<Id<?>><br />{<br /> @SuppressWarnings({ "unchecked", "rawtypes" })<br /> @Override<br /> public Id<?> deserialize(<br /> JsonParser jp, DeserializationContext ctxt) <br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectMapper mapper = (ObjectMapper) jp.getCodec();<br /> ObjectNode node = (ObjectNode) mapper.readTree(jp);<br /> Class<?> classOfId = <br /> mapper.readValue(node.get("classOfId"), Class.class);<br /> long value = mapper.readValue(node.get("value"),long.class);<br /> return new Id(classOfId, value);<br /> }<br />}</pre><b>Additional Code Notes:</b> If <a target="_blank" href="http://jira.codehaus.org/browse/JACKSON-605">Jackson issue 605</a> were implemented, then the custom deserializer would not be necessary, and this solution could be reduced by about 31 lines. Here's what that would look like.<pre name="code" class="java">public class JacksonIdInstanceCreationDemo1<br />{<br /> @SuppressWarnings("unchecked")<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.getSerializationConfig()<br /> .addMixInAnnotations(Id.class, IdCreatorMixIn.class);<br /> mapper.getDeserializationConfig()<br /> .addMixInAnnotations(Id.class, IdCreatorMixIn.class);<br /> <br /> Id<String> idOfString = new Id<String>(String.class, 42);<br /> <br /> // Serializing idOfString<br /> String idOfStringJson =<br /> mapper.writeValueAsString(idOfString);<br /> System.out.println(idOfStringJson);<br /> // output: {"classOfId":"java.lang.String","value":42}<br /> <br /> // Deserializing <br /> // {"classOfId":"java.lang.String","value":42}<br /> // to Id, without parameterized type info<br /> Id<?> idOfStringCopy1 =<br /> mapper.readValue(idOfStringJson, Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy1));<br /> // output: {"classOfId":"java.lang.String","value":42}<br /> <br /> String idOfObjectMapperJson = <br /> setClassOfId(ObjectMapper.class, idOfStringJson, mapper);<br /> <br /> // Deserializing<br /> // {"classOfId":"org.codehaus.jackson.map.ObjectMapper",<br /> // "value":42}<br /> // to Id, without parameterized type info<br /> Id<ObjectMapper> idOfObjectMapper =<br /> mapper.readValue(idOfObjectMapperJson, Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfObjectMapper));<br /> // output: {"value":42,<br /> // "classOfId":"org.codehaus.jackson.map.ObjectMapper"}<br /> <br /> // input: {"value":42}<br /> String json2 = "{\"value\":42}";<br /> <br /> // Fixing {"value":42}<br /> // to {"classOfId":"java.lang.String","value":42}<br /> // Deserializing to Id, without parameterized type info<br /> Id<String> idOfStringCopy3 = mapper.readValue(<br /> setClassOfId(String.class, json2, mapper), Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy3));<br /> // output: {"classOfId":"java.lang.String","value":42}<br /> }<br /><br /> static String setClassOfId(Class<?> value, <br /> String jsonObject, ObjectMapper mapper) throws Exception<br /> {<br /> ObjectNode node = (ObjectNode) mapper.readTree(jsonObject);<br /> node.put("classOfId", value.getName());<br /> return node.toString();<br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br />}<br /><br />// Necessary to expose private fields for serialization.<br />// Deserialization just uses public constructor.<br />@JsonAutoDetect(fieldVisibility = Visibility.ANY)<br />abstract class IdCreatorMixIn<T><br />{<br /> @JsonCreator<br /> public IdCreatorMixIn(@JsonProperty("idOfClass") Class<T> c,<br /> @JsonProperty("value") long v) {}<br />}</pre>This next approach more closely resembles the functionality in the original Gson example, as it uses the parameterized Id type information to determine what <code>Class</code> to assign to "classOfId", disregarding whatever value might be in the input JSON for this field, and allowing the JSON element "classOfId" to be missing or have a null value.<pre name="code" class="java">public class JacksonContextualDeserializerForRootDemo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> SimpleModule module =<br /> new CustomModule("IdDeserializerModule",<br /> new Version(1, 0, 0, null), new IdDeserializers());<br /> module.addDeserializer(Id.class, new IdDeserializer(null));<br /><br /> ObjectMapper mapper = new ObjectMapper().withModule(module);<br /><br /> // Necessary for serialization. <br /> // Deserialization uses public constructor. <br /> mapper.setVisibilityChecker(mapper.getVisibilityChecker()<br /> .withFieldVisibility(Visibility.ANY));<br /><br /> Id<String> idOfString = new Id<String>(String.class, 42);<br /><br /> // Deserializing <br /> // {"classOfId":"java.lang.String","value":42} to Id,<br /> // without parameterized type information<br /> String idOfStringJson=mapper.writeValueAsString(idOfString);<br /> Id<?> idOfStringCopy1 = <br /> mapper.readValue(idOfStringJson, Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy1));<br /> // OUTPUT: {"classOfId":null,"value":42} <br /><br /> // Deserializing <br /> // {"classOfId":"java.lang.String","value":42} <br /> // to Id<String> <br /> TypeReference<Id<String>> idOfStringType =<br /> new TypeReference<Id<String>>() {};<br /> Id<String> idOfStringCopy2 =<br /> mapper.readValue(idOfStringJson, idOfStringType);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy2));<br /> // OUTPUT: {"classOfId":"java.lang.String","value":42}<br /><br /> // Deserializing {"value":42} to Id<String> <br /> String json2 = "{\"value\":42}";<br /> Id<String> idOfStringCopy3 =<br /> mapper.readValue(json2, idOfStringType);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy3));<br /> // OUTPUT: {"classOfId":"java.lang.String","value":42}<br /><br /> // Deserializing {"value":42} to Id<Long> <br /> TypeReference<Id<Long>> idOfLongType =<br /> new TypeReference<Id<Long>>() {};<br /> Id<Long> idOfLong = mapper.readValue(json2, idOfLongType);<br /> System.out.println(mapper.writeValueAsString(idOfLong));<br /> // OUTPUT: {"classOfId":"java.lang.Long","value":42} <br /><br /> // Deserializing <br /> // {"classOfId":"not a null value","value":42} <br /> // to Id<String> <br /> String json3 =<br /> "{\"classOfId\":\"not a null value\",\"value\":42}";<br /> Id<String> idOfStringCopy4 =<br /> mapper.readValue(json3, idOfStringType);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy4));<br /> // OUTPUT: {"classOfId":"java.lang.String","value":42}<br /><br /> // Deserializing <br /> // {"classOfId":"not a null value","value":42}<br /> // to Id<Long> <br /> Id<Long> idOfLong2 = mapper.readValue(json3, idOfLongType);<br /> System.out.println(mapper.writeValueAsString(idOfLong2));<br /> // OUTPUT: {"classOfId":"java.lang.Long","value":42} <br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br />}<br /><br />class IdDeserializer extends JsonDeserializer<Id<?>><br /> implements ContextualDeserializer<Id<?>><br />{<br /> private Class<?> targetClass;<br /><br /> IdDeserializer(Class<?> c) {targetClass = c;}<br /><br /> @Override<br /> public JsonDeserializer<Id<?>> createContextual(<br /> DeserializationConfig config, BeanProperty property)<br /> throws JsonMappingException<br /> {<br /> if (property != null)<br /> {<br /> JavaType type = property.getType();<br /> JavaType ofType = type.containedType(0);<br /> targetClass = ofType.getRawClass();<br /> }<br /> return this;<br /> }<br /><br /> @SuppressWarnings({ "unchecked", "rawtypes" })<br /> @Override<br /> public Id<?> deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectMapper mapper = (ObjectMapper) jp.getCodec();<br /> ObjectNode object = (ObjectNode) mapper.readTree(jp);<br /> long value = object.get("value").getLongValue();<br /> return new Id(targetClass, value);<br /> }<br />}<br /><br />class IdDeserializers extends SimpleDeserializers<br />{<br /> @Override<br /> public JsonDeserializer<?> findBeanDeserializer(<br /> JavaType type, DeserializationConfig config,<br /> DeserializerProvider provider, BeanDescription beanDesc,<br /> BeanProperty property) throws JsonMappingException<br /> {<br /> JsonDeserializer<?> deserializer =<br /> (_classMappings == null) ? null :<br /> _classMappings.get(new ClassKey(type.getRawClass()));<br /> if (deserializer instanceof IdDeserializer && type != null)<br /> {<br /> JavaType ofType = type.containedType(0);<br /> if (ofType != null)<br /> {<br /> Class<?> targetClass = ofType.getRawClass();<br /> deserializer = new IdDeserializer(targetClass);<br /> }<br /> }<br /> return deserializer;<br /> }<br />}<br /><br />class CustomModule extends SimpleModule<br />{<br /> protected final SimpleDeserializers deserializers;<br /><br /> public CustomModule(String name, Version version,<br /> SimpleDeserializers deserializers)<br /> {<br /> super(name, version);<br /> this.deserializers = deserializers;<br /> }<br /><br /> @Override<br /> public <T> SimpleModule addDeserializer(<br /> Class<T> type, JsonDeserializer<? extends T> deser)<br /> {<br /> deserializers.addDeserializer(type, deser);<br /> return this;<br /> }<br /><br /> @Override<br /> public void setupModule(SetupContext context)<br /> {<br /> if (_serializers != null)<br /> context.addSerializers(_serializers);<br /> if (deserializers != null)<br /> context.addDeserializers(deserializers);<br /> if (_keySerializers != null)<br /> context.addKeySerializers(_keySerializers);<br /> if (_keyDeserializers != null)<br /> context.addKeyDeserializers(_keyDeserializers);<br /> if (_abstractTypes != null)<br /> context.addAbstractTypeResolver(_abstractTypes);<br /> }<br />}</pre><b>Additional Code Notes:</b><ul><li>That's a lot of code; 100 lines to do what Gson did in 30. Some of the additional configuration steps are to enable contextual deserialization, which is not enabled by default, in order to provide faster processing for situations that don't need it. Some parts of the additional configuration framework would not be necessary were Jackson enhanced to allow plug-in customizations of the relevant parts. From the bottom up, the following describes what it's all doing.<ul><li>The custom <code>SimpleModule</code> is necessary to insert a custom <code>SimpleDeserializers</code>.</li><li>The custom <code>SimpleDeserializers</code> is necessary to ensure a different custom <code>JsonDeserializer</code> instance is used for each target type.</li><li>The custom <code>JsonDeserializer</code> is necessary to construct parameterized type-specific instances of <code>Id</code>.</li></ul>The end result is a solution that provides a slightly more robust solution than what the original Gson example provided. (This is explained further below.)</li><li>If the next release of Jackson includes an implementation for <a target="_blank" href="https://jira.codehaus.org/browse/JACKSON-599">issue 599</a>, then subclassing SimpleModule will not be necessary. This would reduce the solution by 34 lines.</li><li>I'll probably submit at least two more enhancement requests for simpler contextual deserialization (to remove the necessary custom Deserializers implementation), and for user-customized null or missing element deserialization.</li></ul>This second Jackson approach can be made somewhat more robust, to not require hard-coding of the "classOfId" String literal, by initially constructing a partially-complete Id instance with only the classOfId set, and then using an updating ObjectReader to populate the remaining field from the JSON, without needing to access the JSON element explicitly by name. Here's what that solution would look like.<pre name="code" class="java">public class JacksonContextualDeserializerForRootDemo2<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper2 = new ObjectMapper();<br /> mapper2.getDeserializationConfig()<br /> .addMixInAnnotations(Id.class, IdCreatorMixIn.class);<br /> <br /> SimpleModule module =<br /> new CustomModule("IdDeserializerModule",<br /> new Version(1, 0, 0, null), new IdDeserializers());<br /> module.addDeserializer(<br /> Id.class, new IdDeserializer(mapper2, null));<br /><br /> ObjectMapper mapper = new ObjectMapper().withModule(module);<br /> mapper.getSerializationConfig()<br /> .addMixInAnnotations(Id.class, IdSerializerMixIn.class);<br /><br /> Id<String> idOfString = new Id<String>(String.class, 42);<br /><br /> // Deserializing <br /> // {"classOfId":"java.lang.String","value":42} to Id,<br /> // without parameterized type information<br /> String idOfStringJson=mapper.writeValueAsString(idOfString);<br /> Id<?> idOfStringCopy1 = <br /> mapper.readValue(idOfStringJson, Id.class);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy1));<br /> // OUTPUT: {"classOfId":null,"value":42} <br /><br /> // Deserializing <br /> // {"classOfId":"java.lang.String","value":42} <br /> // to Id<String> <br /> TypeReference<Id<String>> idOfStringType =<br /> new TypeReference<Id<String>>() {};<br /> Id<String> idOfStringCopy2 =<br /> mapper.readValue(idOfStringJson, idOfStringType);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy2));<br /> // OUTPUT: {"classOfId":"java.lang.String","value":42}<br /><br /> // Deserializing {"value":42} to Id<String> <br /> String json2 = "{\"value\":42}";<br /> Id<String> idOfStringCopy3 =<br /> mapper.readValue(json2, idOfStringType);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy3));<br /> // OUTPUT: {"classOfId":"java.lang.String","value":42}<br /><br /> // Deserializing {"value":42} to Id<Long> <br /> TypeReference<Id<Long>> idOfLongType =<br /> new TypeReference<Id<Long>>() {};<br /> Id<Long> idOfLong = mapper.readValue(json2, idOfLongType);<br /> System.out.println(mapper.writeValueAsString(idOfLong));<br /> // OUTPUT: {"classOfId":"java.lang.Long","value":42} <br /><br /> // Deserializing <br /> // {"classOfId":"not a null value","value":42} <br /> // to Id<String> <br /> String json3 =<br /> "{\"classOfId\":\"not a null value\",\"value\":42}";<br /> Id<String> idOfStringCopy4 =<br /> mapper.readValue(json3, idOfStringType);<br /> System.out.println(<br /> mapper.writeValueAsString(idOfStringCopy4));<br /> // OUTPUT: {"classOfId":"java.lang.String","value":42}<br /><br /> // Deserializing <br /> // {"classOfId":"not a null value","value":42}<br /> // to Id<Long> <br /> Id<Long> idOfLong2 = mapper.readValue(json3, idOfLongType);<br /> System.out.println(mapper.writeValueAsString(idOfLong2));<br /> // OUTPUT: {"classOfId":"java.lang.Long","value":42} <br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br />}<br /><br />// Necessary to expose private fields for serialization.<br />@JsonAutoDetect(fieldVisibility = Visibility.ANY)<br />abstract class IdSerializerMixIn<T> {}<br /><br />// Necessary to skip setting classOfId during deserialization.<br />@JsonAutoDetect(fieldVisibility = Visibility.ANY)<br />abstract class IdCreatorMixIn<T><br />{<br /> @JsonIgnore private final Class<T> classOfId = null;<br />}<br /><br />class IdDeserializer extends JsonDeserializer<Id<?>><br /> implements ContextualDeserializer<Id<?>><br />{<br /> private final ObjectMapper mapper;<br /> private Class<?> targetClass;<br /><br /> IdDeserializer(ObjectMapper mapper, Class<?> c)<br /> {<br /> this.mapper = mapper;<br /> this.targetClass = c;<br /> }<br /> <br /> ObjectMapper getMapper() {return mapper;}<br /><br /> @Override<br /> public JsonDeserializer<Id<?>> createContextual(<br /> DeserializationConfig config, BeanProperty property)<br /> throws JsonMappingException<br /> {<br /> if (property != null)<br /> {<br /> JavaType type = property.getType();<br /> JavaType ofType = type.containedType(0);<br /> targetClass = ofType.getRawClass();<br /> }<br /> return this;<br /> }<br /><br /> @SuppressWarnings({ "unchecked", "rawtypes" })<br /> @Override<br /> public Id<?> deserialize(<br /> JsonParser jp, DeserializationContext ctxt)<br /> throws IOException, JsonProcessingException<br /> {<br /> Id id = new Id(targetClass, 0);<br /> return mapper.updatingReader(id).readValue(jp);<br /> }<br />}<br /><br />class IdDeserializers extends SimpleDeserializers<br />{<br /> @Override<br /> public JsonDeserializer<?> findBeanDeserializer(<br /> JavaType type, DeserializationConfig config,<br /> DeserializerProvider provider, BeanDescription beanDesc,<br /> BeanProperty property) throws JsonMappingException<br /> {<br /> JsonDeserializer<?> deserializer =<br /> (_classMappings == null) ? null :<br /> _classMappings.get(new ClassKey(type.getRawClass()));<br /> if (deserializer instanceof IdDeserializer && type != null)<br /> {<br /> JavaType ofType = type.containedType(0);<br /> if (ofType != null)<br /> {<br /> Class<?> targetClass = ofType.getRawClass();<br /> deserializer = new IdDeserializer(<br /> ((IdDeserializer)deserializer).getMapper(), targetClass);<br /> }<br /> }<br /> return deserializer;<br /> }<br />}<br /><br />class CustomModule extends SimpleModule<br />{<br /> protected final SimpleDeserializers deserializers;<br /><br /> public CustomModule(String name, Version version,<br /> SimpleDeserializers deserializers)<br /> {<br /> super(name, version);<br /> this.deserializers = deserializers;<br /> }<br /><br /> @Override<br /> public <T> SimpleModule addDeserializer(<br /> Class<T> type, JsonDeserializer<? extends T> deser)<br /> {<br /> deserializers.addDeserializer(type, deser);<br /> return this;<br /> }<br /><br /> @Override<br /> public void setupModule(SetupContext context)<br /> {<br /> if (_serializers != null)<br /> context.addSerializers(_serializers);<br /> if (deserializers != null)<br /> context.addDeserializers(deserializers);<br /> if (_keySerializers != null)<br /> context.addKeySerializers(_keySerializers);<br /> if (_keyDeserializers != null)<br /> context.addKeyDeserializers(_keyDeserializers);<br /> if (_abstractTypes != null)<br /> context.addAbstractTypeResolver(_abstractTypes);<br /> }<br />}</pre>In the custom deserializer, instead of using the original ObjectMapper available through the JsonParser, use of the second ObjectMapper instance is necessary, because the original ObjectMapper is configured to just pass deserialization back to the custom deserializer (by calling a different method that we've chosen not to implement). It's possible to handle this call back, but then manual parsing would be necessary, including explicit reference to the JSON element "classOfId" by name, which would defeat the purpose of this second approach (which is to not need hard-coded reference of "classOfId").<br /><br />Going back to the first Gson demo in this section of the Gson user guide -- we're still in the "InstanceCreator for a Parameterized Type" section -- as written, it actually fails, throwing an exception, if the JSON contains a non-null value for the "classOfId" element. Also, if the JSON contains a null value for the "classOfId" element, then the Gson solution replaces the <code>Id.classOfId</code> value set by the <code>InstanceCreator</code> with the null value from the JSON. This happens because Gson first uses the instance creator to create an instance of the target type with any state (i.e., properties values) specified by the instance creator, and then Gson replaces that state, as appropriate, with values from the JSON. As mentioned, this means that the "classOfId" field is overwritten if the JSON has an element of the same name (unless something is explicitly done to stop this from happening). To further clarify this point, following is a demo of these shortcomings.<pre name="code" class="java"> // input: {"classOfId":"java.lang.Long","value":42}<br /> String json3 = <br /> "{\"classOfId\":\"java.lang.Long\",\"value\":42}";<br /> // Id<String> id1Copy2 = gson.fromJson(json3, idOfStringType);<br /> // throws RuntimeException: <br /> // Unable to invoke no-args constructor for <br /> // java.lang.Class<java.lang.String><br /> <br /> // input: {"classOfId":null,"value":42}<br /> String json4 = "{\"classOfId\":null,\"value\":42}";<br /> Id<String> id1Copy3 = gson.fromJson(json4, idOfStringType);<br /> System.out.println(id1Copy3);<br /> // output: classOfId=null, value=42</pre>A decent solution to solve these shortcomings would be to implement a custom deserializer for Id, instead of a custom instance creator. The following code demonstrates this alternative, more robust implementation.<pre name="code" class="java">public class GsonInstanceCreatorForParameterizedTypeDemo2<br />{<br /> public static void main(String[] args)<br /> {<br /> Id<String> id1 = new Id<String>(String.class, 42);<br /><br /> Gson gson = new GsonBuilder().registerTypeAdapter(Id.class,<br /> new IdDeserializer()).create();<br /> String json1 = gson.toJson(id1);<br /> System.out.println(json1);<br /> // actual output: {"classOfId":{},"value":42}<br /> // This contradicts what the Gson docs say happens.<br /> // With custom serialization, as described in a<br /> // previous Gson user guide section, <br /> // intended output may be <br /> // {"value":42}<br /><br /> // input: {"value":42}<br /> String json2 = "{\"value\":42}";<br /><br /> Type idOfStringType=new TypeToken<Id<String>>(){}.getType();<br /> Id<String> id1Copy = gson.fromJson(json2, idOfStringType);<br /> System.out.println(id1Copy);<br /> // output: classOfId=class java.lang.String, value=42<br /><br /> Type idOfGsonType = new TypeToken<Id<Gson>>() {}.getType();<br /> Id<Gson> idOfGson = gson.fromJson(json2, idOfGsonType);<br /> System.out.println(idOfGson);<br /> // output: classOfId=class com.google.gson.Gson, value=42<br /> <br /> // input: {"classOfId":"java.lang.Long","value":42}<br /> String json3 = <br /> "{\"classOfId\":\"java.lang.Long\",\"value\":42}";<br /> Id<String> id1Copy2 = gson.fromJson(json3, idOfStringType);<br /> System.out.println(id1Copy2);<br /> // output: classOfId=class java.lang.String, value=42<br /> <br /> // input: {"classOfId":null,"value":42}<br /> String json4 = "{\"classOfId\":null,\"value\":42}";<br /> Id<String> id1Copy3 = gson.fromJson(json4, idOfStringType);<br /> System.out.println(id1Copy3);<br /> // output: classOfId=class java.lang.String, value=42<br /> <br /> // input: {"classOfId":"a non-null value","value":42}<br /> String json5 = <br /> "{\"classOfId\":\"a non-null value\",\"value\":42}";<br /> Id<Gson> idOfGson2 = gson.fromJson(json5, idOfGsonType);<br /> System.out.println(idOfGson2);<br /> // output: classOfId=class com.google.gson.Gson, value=42<br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br /><br /> @Override<br /> public String toString()<br /> {<br /> return "classOfId=" + classOfId + ", value=" + value;<br /> }<br />}<br /><br />class IdDeserializer implements JsonDeserializer<Id<?>><br />{<br /> @SuppressWarnings({ "unchecked", "rawtypes" })<br /> @Override<br /> public Id<?> deserialize(JsonElement json, Type typeOfT,<br /> JsonDeserializationContext context)<br /> throws JsonParseException<br /> {<br /> long value =json.getAsJsonObject().get("value").getAsLong();<br /> Type[] typeParameters = <br /> ((ParameterizedType) typeOfT).getActualTypeArguments();<br /> Type idType = typeParameters[0];<br /> return new Id((Class<?>) idType, value);<br /> }<br />}</pre>If the Id class definition could be modified, which violates a condition specified by the Gson user guide for this section that the Id class was part of an API that could not be modified, then the <code>@Expose</code> annotation (covered in more detail in <a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html">part 4</a> of this series) could be employed to similarly skip replacing the value of classOfId, initially set by the instance creator, with whatever is in the JSON. (If Gson had a facility to mix in annotations like Jackson does, then modifying the Id class definition would not be necessary for this approach.) Here's what that solution would look like.<pre name="code" class="java">public class GsonInstanceCreatorForParameterizedTypeDemo3<br />{<br /> public static void main(String[] args)<br /> {<br /> Id<String> id1 = new Id<String>(String.class, 42);<br /><br /> Gson gson = new GsonBuilder()<br /> .registerTypeAdapter(Id.class, new IdInstanceCreator())<br /> .excludeFieldsWithoutExposeAnnotation().create();<br /> String json1 = gson.toJson(id1);<br /> System.out.println(json1);<br /> // actual output: {"classOfId":{},"value":42}<br /> // This contradicts what the Gson docs say happens.<br /> // With custom serialization, as described in a<br /> // previous Gson user guide section, <br /> // intended output may be <br /> // {"value":42}<br /><br /> // input: {"value":42}<br /> String json2 = "{\"value\":42}";<br /><br /> Type idOfStringType=new TypeToken<Id<String>>(){}.getType();<br /> Id<String> id1Copy = gson.fromJson(json2, idOfStringType);<br /> System.out.println(id1Copy);<br /> // output: classOfId=class java.lang.String, value=42<br /><br /> Type idOfGsonType = new TypeToken<Id<Gson>>() {}.getType();<br /> Id<Gson> idOfGson = gson.fromJson(json2, idOfGsonType);<br /> System.out.println(idOfGson);<br /> // output: classOfId=class com.google.gson.Gson, value=42<br /> <br /> // input: {"classOfId":"java.lang.Long","value":42}<br /> String json3 = <br /> "{\"classOfId\":\"java.lang.Long\",\"value\":42}";<br /> Id<String> id1Copy2 = gson.fromJson(json3, idOfStringType);<br /> System.out.println(id1Copy2);<br /> // output: classOfId=class java.lang.String, value=42<br /> <br /> // input: {"classOfId":null,"value":42}<br /> String json4 = "{\"classOfId\":null,\"value\":42}";<br /> Id<String> id1Copy3 = gson.fromJson(json4, idOfStringType);<br /> System.out.println(id1Copy3);<br /> // output: classOfId=class java.lang.String, value=42<br /> <br /> // input: {"classOfId":"a non-null value","value":42}<br /> String json5 = <br /> "{\"classOfId\":\"a non-null value\",\"value\":42}";<br /> Id<Gson> idOfGson2 = gson.fromJson(json5, idOfGsonType);<br /> System.out.println(idOfGson2);<br /> // output: classOfId=class com.google.gson.Gson, value=42<br /> }<br />}<br /><br />class Id<T><br />{<br /> private final Class<T> classOfId;<br /> @Expose private final long value;<br /><br /> public Id(Class<T> classOfId, long value)<br /> {<br /> this.classOfId = classOfId;<br /> this.value = value;<br /> }<br /><br /> @Override<br /> public String toString()<br /> {<br /> return "classOfId=" + classOfId + ", value=" + value;<br /> }<br />}<br /><br />class IdInstanceCreator implements InstanceCreator<Id<?>><br />{<br /> @SuppressWarnings({ "unchecked", "rawtypes" })<br /> public Id<?> createInstance(Type type)<br /> {<br /> Type[] typeParameters =<br /> ((ParameterizedType) type).getActualTypeArguments();<br /> Type idType = typeParameters[0];<br /> return new Id((Class<?>) idType, 0L);<br /> }<br />}</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE in ability to deserialize with parameterized types</li><li>+1 Gson for simple, contextual deserialization with parameterized type information</li><li>COMPARABLE in ability to accommodate missing JSON element values -- Gson uses an <code>InstanceCreator</code> to set the default value and further deserialization processing to overwrite value from JSON as appropriate; Jackson uses an updating reader for the same effect</li></ul><br /><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-4.html">Continue to part 4...</a><br /><br /><h2>References And Resources:</h2><ul><li><a target="_blank" href="http://code.google.com/p/google-gson">Gson Project Home</a></li><li><a target="_blank" href="http://jackson.codehaus.org">Jackson Project Home</a></li><li><a target="_blank" href="https://groups.google.com/forum/#!forum/google-gson">Gson User Group Mailing List</a></li><li><a target="_blank" href="http://jackson-users.ning.com">Jackson JSON User Group</a></li><li><a target="_blank" href="http://code.google.com/p/google-gson/source/checkout">The Gson Source Code</a></li><li><a target="_blank" href="http://svn.codehaus.org/jackson/">The Jackson Source Code</a></li></ul>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com3tag:blogger.com,1999:blog-5325426185501267599.post-64128178414123560682011-06-27T17:01:00.011-05:002011-07-11T05:29:50.470-05:00Gson v Jackson - Part 2<table border="0"><tbody><tr><td><h2>tl;dnr</h2>Use Jackson, not Gson. Use this article as a reference for basic features.<br /><br /><a target="_blank" href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson.html">Part 1</a> of this short series of articles ended with section 5.3 of the Gson user guide, titled "Nested Classes (including Inner Classes)". This second part continues with section 5.4 on "Array Examples", and ends with section 5.7 on "Serializing and Deserializing Collection with Objects of Arbitrary Types". Part 3 will continue with section 5.8 on "Built-in Serializers and Deserializers".</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/qkceb" alt="QR Code Link To This Article" border="0" />http://goo.gl/qkceb</div></td></tr></tbody></table><a name='more'></a><br />See <a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-6.html">part 6</a> of this series for a complete listing of and links to the various sections of this Gson user guide review.<br /><br />For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at <a href="https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip">https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip</a>.)<br /><br />This information is based on Gson release 1.7.1 and Jackson release 1.8.2.<br /><br /><h2>The Gson User Guide Walk-through Continued...</h2><br /><a name="TOC-Array-Examples"><h3>Array Examples</h3></a><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Array-Examples">relevant section in the Gson user guide</a>.<br /><br /><b>The comparable Jackson Code:</b><pre name="code" class="java">ObjectMapper mapper = new ObjectMapper();<br />int[] ints = {1, 2, 3, 4, 5};<br />String[] strings = {"abc", "def", "ghi"};<br /><br />// (Serialization)<br />System.out.println(mapper.writeValueAsString(ints));<br />// output: [1,2,3,4,5]<br />System.out.println(mapper.writeValueAsString(strings));<br />// output: ["abc","def","ghi"]<br /><br />// (Deserialization)<br />int[] ints2 = mapper.readValue("[1,2,3,4,5]", int[].class);<br />System.out.println(Arrays.toString(ints2));<br />// output: [1, 2, 3, 4, 5]</pre><b>Comparison Rating:</b> COMPARABLE<br /><br /><b>Additional Section Notes:</b> This Gson user guide section ends with the statement, "We also support multi-dimensional arrays, with arbitrarily complex element types." This is unfortunately not an entirely correct statement, if it is applied to the context of data-binding, in which it was presented. The statement should be modified to read, "We also provide limited support for multi-dimensional arrays, with limited support for arbitrarily complex element types."<br /><br />Examples to support this include the following.<pre name="code" class="java">Gson gson = new Gson();<br />ObjectMapper mapper = new ObjectMapper();<br /><br />// 1-dimension array with varying primitive type elements<br />// input: ["abc",42]<br />String json1 = "[\"abc\",42]";<br /><br />Object[] things1a = gson.fromJson(json1, Object[].class);<br />System.out.println(Arrays.toString(things1a));<br />// output: [abc, 42]<br /><br />Object[] things1b = mapper.readValue(json1, Object[].class);<br />System.out.println(Arrays.toString(things1b));<br />// output: [abc, 42]<br /><br />// 2x2 array with varying primitive type elements<br />// input: [["abc",42],["def",43]]<br />String json2 = "[[\"abc\",42],[\"def\",43]]";<br /><br />Object[][] things2a1 = gson.fromJson(json2, Object[][].class);<br />System.out.println(Arrays.toString(things2a1[0]));<br />// output: [abc, 42]<br />System.out.println(Arrays.toString(things2a1[1]));<br />// output: [def, 43]<br /><br />// throws JsonParseException, complaining:<br />// Type information is unavailable, <br />// and the target is not a primitive: ["abc",42]<br />// Object[] things2a2 = gson.fromJson(json2, Object[].class);<br /><br />// throws JsonParseException, complaining:<br />// Type information is unavailable, <br />// and the target is not a primitive: [["abc",42],["def",43]]<br />// Object things2a3 = gson.fromJson(json2, Object.class);<br /><br />Object[][] things2b1 = <br /> mapper.readValue(json2, Object[][].class);<br />System.out.println(Arrays.toString(things2b1[0]));<br />// output: [abc, 42]<br />System.out.println(Arrays.toString(things2b1[1]));<br />// output: [def, 43]<br /><br />Object[] things2b2 = mapper.readValue(json2, Object[].class);<br />System.out.println(Arrays.toString(things2b2));<br />// output: [[abc, 42], [def, 43]]<br />System.out.println(things2b2[0].getClass());<br />// output: class java.util.ArrayList<br /><br />Object things2b3 = mapper.readValue(json2, Object.class);<br />System.out.println(things2b3);<br />// output: [[abc, 42], [def, 43]]<br />System.out.println(things2b3.getClass());<br />// output: class java.util.ArrayList<br /><br />// "uneven" multi-dimensional array<br />// input: [["abc",[42,24]],["def",[43,34]]]<br />String json3 = "[[\"abc\",[42,24]],[\"def\",[43,34]]]";<br /><br />// throws JsonParseException, complaining: <br />// Type information is unavailable, <br />// and the target is not a primitive: [42,24]<br />// Object[][] things3a1 = <br />// gson.fromJson(json3, Object[][].class);<br /><br />// throws JsonParseException, complaining: <br />// Type information is unavailable, <br />// and the target is not a primitive: ["abc",[42,24]]<br />// Object[] things3a2 = gson.fromJson(json3, Object[].class);<br /><br />// throws JsonParseException, complaining: <br />// Type information is unavailable, <br />// and the target is not a primitive: <br />// [["abc",[42,24]],["def",[43,34]]]<br />// Object things3a3 = gson.fromJson(json3, Object.class);<br /><br />// throws RuntimeException<br />// Object[][][] things3a4 = <br />// gson.fromJson(json3, Object[][][].class);<br /><br />Object[][] things3b1 = <br /> mapper.readValue(json3, Object[][].class);<br />System.out.println(things3b1[0][0].getClass());<br />// output: class java.lang.String<br />System.out.println(things3b1[0][1].getClass());<br />// output: class java.util.ArrayList<br />System.out.println(Arrays.toString(things3b1[0]));<br />// output: [abc, [42, 24]]<br />System.out.println(Arrays.toString(things3b1[1]));<br />// output: [def, [43, 34]]<br /><br />Object[] things3b2 = mapper.readValue(json3, Object[].class);<br />System.out.println(Arrays.toString(things3b2));<br />// output: [[abc, [42, 24]], [def, [43, 34]]]<br /><br />Object things3b3 = mapper.readValue(json3, Object.class);<br />System.out.println(things3b3);<br />// [[abc, [42, 24]], [def, [43, 34]]]<br /><br />mapper.configure(<br /> DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY,<br /> true);<br />Object[][][] things3b4 = <br /> mapper.readValue(json3, Object[][][].class);<br />System.out.println(things3b4.length); // 2<br />System.out.println(things3b4[0].length); // 2<br />System.out.println(things3b4[0][0].length); // 1<br />System.out.println(things3b4[0][1].length); // 2<br />System.out.println(things3b4[1].length); // 2<br />System.out.println(things3b4[1][0].length); // 1<br />System.out.println(things3b4[1][1].length); // 2<br />System.out.println(things3b4[0][0][0].getClass());<br />// output: class java.lang.String<br />System.out.println(things3b4[0][1][0].getClass());<br />// output: class java.lang.Integer<br />System.out.println(things3b4[0][1][1].getClass());<br />// output: class java.lang.Integer<br />for (int i = 0; i < things3b4.length; i++)<br /> for (int ii = 0; ii < things3b4[i].length; ii++)<br /> for (int iii = 0; iii < things3b4[i][ii].length; iii++)<br /> System.out.print(things3b4[i][ii][iii] + " ");<br />// output: abc 42 24 def 43 34 <br /><br />System.out.println();<br />mapper = new ObjectMapper();<br /><br />// input: [42,{"one":1}]<br />String json4 = "[42,{\"one\":1}]";<br /><br />// throws JsonParseException, complaining: <br />// Type information is unavailable, <br />// and the target is not a primitive: [42,{"one":1}]<br />// Object things4a1 = gson.fromJson(json4, Object.class);<br /><br />// throws JsonParseException, complaining: <br />// Type information is unavailable, <br />// and the target object is not a primitive: {"one":1}<br />// Object[] things4a2 = gson.fromJson(json4, Object[].class);<br /><br />Object things4b1 = mapper.readValue(json4, Object.class);<br />System.out.println(things4b1);<br />// output: [42, {one=1}]<br />System.out.println(things4b1.getClass());<br />// output: class java.util.ArrayList<br />List things4b1List = (List) things4b1;<br />System.out.println(things4b1List.size()); // 2<br />System.out.println(things4b1List.get(0).getClass());<br />// output: class java.lang.Integer<br />System.out.println(things4b1List.get(1).getClass());<br />// output: class java.util.LinkedHashMap<br /><br />Object[] things4b2 = mapper.readValue(json4, Object[].class);<br />System.out.println(Arrays.toString(things4b2));<br />// output: [42, {one=1}]<br />System.out.println(things4b2[0].getClass());<br />// class java.lang.Integer<br />System.out.println(things4b2[1].getClass());<br />// class java.util.LinkedHashMap</pre><b>Additional Code Notes:</b><ul><li>These are not a contrived examples. The structures are based on real-world JSON.</li><li>How to implement custom deserialization processing with Gson to handle these examples is briefly described in <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Colle">the "Serializing and Deserializing Collection with Objects of Arbitrary Types" section</a> of the Gson user guide.</li></ul><br /><b>Comparison Ratings:</b><ul><li>+1 Jackson for significantly more complete data-binding support of multi-dimensional arrays</li><li>+1 Jackson for significantly more complete data-binding support of arbitrarily complex array element types</li></ul><br /><a name="TOC-Collections-Examples"><h3>Collections Examples</h3></a><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Collections-Examples">relevant section in the Gson user guide</a>.<br /><br /><b>The comparable Jackson Code:</b><pre name="code" class="java">ObjectMapper mapper = new ObjectMapper();<br />Collection<Integer> ints = Arrays.asList(1,2,3,4,5);<br /><br />// (Serialization)<br />String json = mapper.writeValueAsString(ints);<br />System.out.println(json); // [1,2,3,4,5]<br /><br />// (Deserialization)<br />TypeReference collectionType = <br /> new TypeReference<Collection<Integer>>(){};<br />Collection<Integer> ints2 = <br /> mapper.readValue(json, collectionType);<br />System.out.println(ints2); // [1, 2, 3, 4, 5]<br /><br />CollectionType collectionType2 = <br /> TypeFactory.defaultInstance().constructCollectionType(<br /> Collection.class, Integer.class);<br />Collection<Integer> ints3 = <br /> mapper.readValue(json, collectionType2);<br />System.out.println(ints3); // [1, 2, 3, 4, 5]</pre><b>Code Notes:</b> As demonstrated, Jackson offers two ways to solve the deserialization to generic types problem.<br /><br /><b>Comparison Rating:</b> COMPARABLE<br /><br /><a name="TOC-Collections-Limitations"><h3>Collections Limitations</h3></a><br />Concerning the Gson user guide statements on collections limitations, including that Gson "[c]an serialize [a] collection of arbitrary objects but can not deserialize from it," note that Jackson is not similarly deficient, provided that polymorphic type information is available in the JSON and that other necessary configurations are made. For examples of deserializing JSON into polymorphic structures with Jackson, see <a target="_blank" href="http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html">http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html</a>.<br /><br /><b>Comparison Rating:</b> +1 Jackson for built-in polymorphic deserialization capability****<br /><br />****The next release of Gson is planned to included some built-in support for polymorphic deserialization, as described in <a target="_blank" href="http://code.google.com/p/google-gson/issues/detail?id=231">issue 231</a>.<br /><br /><a name="TOC-Serializing-and-Deserializing-Gener"><h3>Serializing and Deserializing Generic Types</h3></a><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Gener">relevant section in the Gson user guide</a>.<br /><br />Note that the guide's statement that <code>gson.toJson(myStrings);</code> will cause a runtime exception is wrong, depending on the actual type of the list instance. The following demonstrates this point using an ArrayList.<pre name="code" class="java">List<String> myStrings = new ArrayList<String>();<br />myStrings.add("one");<br />myStrings.add("two");<br />myStrings.add("three");<br /><br />Gson gson = new Gson();<br /><br />String json1 = gson.toJson(myStrings);<br />System.out.println(json1);<br />// output: ["one","two","three"]<br /><br />List<String> myStrings2 = <br /> gson.fromJson(json1, myStrings.getClass());<br />System.out.println(myStrings2);<br />// output: [java.lang.Object@27ce2dd4, <br />// java.lang.Object@5122cdb6, java.lang.Object@43ef9157]<br />Object o1 = myStrings2.get(0);<br />System.out.println(o1.getClass());<br />// class java.lang.Object<br /><br />ObjectMapper mapper = new ObjectMapper();<br /><br />String json2 = mapper.writeValueAsString(myStrings);<br />System.out.println(json2);<br />// output: ["one","two","three"]<br /><br />List<String> myStrings3 = <br /> mapper.readValue(json2, myStrings.getClass());<br />System.out.println(myStrings3);<br />// output: [one, two, three]<br />Object o2 = myStrings3.get(0);<br />System.out.println(o2.getClass());<br />// output: class java.lang.String</pre><b>Additional Notes:</b><ul><li>Note that Gson populated the deserialized list with useless Object instances, retaining none of the data from the JSON input. This is a problem that Gson also has with non-generic Map collections. (<a target="_blank" href="http://code.google.com/p/google-gson/issues/detail?id=325">See issue 325 for details.</a>)</li><li>The use of a TypeToken to resolve this issue, and the Jackson counter parts, were demonstrated in <a href="#TOC-Collections-Examples">the Collections Examples section</a>.</li></ul><br /><b>Comparison Rating:</b> +1 Jackson for significantly better support of deserializing to non-generic collections<br /><br /><a name="TOC-Serializing-and-Deserializing-Colle"><h3>Serializing and Deserializing Collection with Objects of Arbitrary Types</h3></a><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Colle">relevant section in the Gson user guide</a>.<br /><br />Differing from Gson, Jackson can deserialize the example JSON to a collection, without generic type information, and without implementing any of the suggested custom processing solutions.<br /><br /><b>The Jackson Code:</b><pre name="code" class="java">ObjectMapper mapper = new ObjectMapper();<br /><br />Collection collection = new ArrayList();<br />collection.add("hello");<br />collection.add(5);<br />collection.add(new Event("GREETINGS", "guest"));<br /><br />System.out.println(mapper.writeValueAsString(collection));<br />// output: ["hello",5,{"name":"GREETINGS","source":"guest"}]<br /><br />// input: ["hello",5,{"name":"GREETINGS","source":"guest"}]<br />String json = <br />"[\"hello\",5,{\"name\":\"GREETINGS\",\"source\":\"guest\"}]";<br /><br />Collection things = mapper.readValue(json, Collection.class);<br />System.out.println(things);<br />// output: [hello, 5, {name=GREETINGS, source=guest}]</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE for similar support to serialize collections with arbitrary types</li><li>+1 Jackson for significantly better support of deserializing to non-generic collections</li></ul><br /><a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-3.html">Continue to part 3...</a><br /><br /><h2>References And Resources:</h2><ul><li><a target="_blank" href="http://code.google.com/p/google-gson">Gson Project Home</a></li><li><a target="_blank" href="http://jackson.codehaus.org">Jackson Project Home</a></li><li><a target="_blank" href="https://groups.google.com/forum/#!forum/google-gson">Gson User Group Mailing List</a></li><li><a target="_blank" href="http://jackson-users.ning.com">Jackson JSON User Group</a></li><li><a target="_blank" href="http://code.google.com/p/google-gson/source/checkout">The Gson Source Code</a></li><li><a target="_blank" href="http://svn.codehaus.org/jackson/">The Jackson Source Code</a></li></ul>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0tag:blogger.com,1999:blog-5325426185501267599.post-13184230585369495522011-06-25T17:45:00.024-05:002011-07-11T05:31:59.042-05:00Gson v Jackson - Part 1<table border="0"><tbody><tr><td><h2>tl;dnr</h2>Use Jackson, not Gson. Use this article as a reference for basic features.<br /><br /><h2>What is this post?</h2>Is this yet <a target="_blank" href="http://stackoverflow.com/questions/2378402/jackson-vs-gson">another</a> <a target="_blank" href="http://stackoverflow.com/questions/1688099/converting-json-to-java">JSON-to/from-Java</a> <a target="_blank" href="http://stackoverflow.com/questions/338586/a-better-java-json-library">API comparison</a>? Yes, it is. It's the most comprehensive comparison of using Gson versus Jackson for common JSON-to/from-Java tasks known. The sections below walk through each section of <a target="_blank" href="http://sites.google.com/site/gson/gson-user-guide">the Gson user guide</a>, demonstrating similar implementations with Jackson. API features beyond what is described in the Gson user guide are then reviewed.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/YuYhx" alt="QR Code Link To This Article" border="0" />http://goo.gl/YuYhx</div></td></tr></tbody></table><a name='more'></a><br />See <a href="http://programmerbruce.blogspot.com/2011/07/gson-v-jackson-part-6.html">part 6</a> of this series for a complete listing of and links to the various sections of this Gson user guide review.<br /><br />Part 1 of this short series of articles ends with section 5.3 of the Gson user guide, titled "Nested Classes (including Inner Classes)". For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at <a href="https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip">https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip</a>.)<br /><br />This information is based on Gson release 1.7.1 and Jackson release 1.8.2.<br /><br /><h2>The Gson User Guide Walk-through</h2>The Gson user guide outlines many data-binding features. It does not cover in detail the streaming API, the tree model API, "manual" JSON parsing, or "manual" JSON generation, one token at a time.<br /><br />The following sections demonstrate comparable Jackson-based solutions for the examples in the Gson user guide.<br /><br /><a name="TOC-Using-Gson"><h3>Using Gson</h3></a><br />The basic component to map JSON to Java with Gson is named simply "Gson". Its creation and configuration is described in the user guide: "The primary class to use is Gson which you can just create by calling new Gson(). There is also a class GsonBuilder available that can be used to create a Gson instance with various settings..." The comparable constructs in Jackson are ObjectMapper and its configuration options.<br /><br /><b>The Gson Code:</b><pre name="code" class="java">Gson gson = new Gson();<br />// OR<br />GsonBuilder gsonBuilder = new GsonBuilder();<br />gsonBuilder.[specify configs];<br />Gson gson = gsonBuilder.create();</pre><b>Typical comparable Jackson Code:</b><pre name="code" class="java">ObjectMapper mapper = new ObjectMapper();<br />// OR<br />ObjectMapper mapper = new ObjectMapper();<br />mapper.[specify configs];</pre>A key difference between the two approaches just demonstrated is that the <code>Gson</code> instance is immutable (and thus thread-safe in that different threads using the same instance cannot inadvertantly affect the results in other threads), but the <code>ObjectMapper</code> instance is not. To create immutable, thread-safe JSON readers and writers with Jackson, one can use the initial <code>ObjectMapper</code> like a builder, to specify the desired serialization/deserialization configurations, and then use it to generate an <code>ObjectReader</code> and/or an <code>ObjectWriter</code>. For example:<pre name="code" class="java"> ObjectMapper mapper = new ObjectMapper();<br /> // configure ObjectMapper as desired<br /><br /> // To create thread-safe immutable JSON reader and writer:<br /> ObjectReader reader = mapper.reader();<br /> ObjectWriter writer = mapper.writer();</pre>For convenience, most or all of the remaining examples in this series just use <code>ObjectMapper</code> to read and write JSON content.<br /><br /><b>Additional Comparison Notes:</b> There are minor differences between these approaches and implementations. (For example, as implemented for Jackson, the <code>ObjectWriter</code> and <code>ObjectReader</code> instances can be used as base configurations to then generate new instances with new additional configurations applied. The current <code>GsonBuilder/Gson</code> API does not provide this same functionality.) Nothing significant to the Gson or Jackson end user.<br /><br /><b>Comparison Rating:</b> COMPARABLE<br /><br /><a name="TOC-Primitives-Examples"><h3>Primitives Examples - Serialization</h3></a><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Primitives-Examples">relevant section in the Gson user guide</a>.<br /><br /><b>The comparable Jackson Code:</b><pre name="code" class="java">ObjectMapper mapper = new ObjectMapper();<br />System.out.println(mapper.writeValueAsString(1));<br />System.out.println(mapper.writeValueAsString("abcd"));<br />System.out.println(mapper.writeValueAsString(new Long(10)));<br />int[] values = {1};<br />System.out.println(mapper.writeValueAsString(values));</pre><b>Comparison Notes:</b> The results of the Jackson solution are identical with that of the Gson solution. Jackson makes it very easy to work with streams, writers, strings, and files. So, in some situations it requires somewhat more verbose coding. With this example, the difference to use a longer method name is insignificant.<br /><br /><b>Comparison Rating:</b> COMPARABLE<br /><br /><h3>Primitives Examples - Deserialization</h3><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Primitives-Examples">relevant section in the Gson user guide</a>.<br /><br /><b>The comparable Jackson Code:</b><pre name="code" class="java">ObjectMapper mapper = new ObjectMapper();<br />int one_i = mapper.readValue("1", int.class);<br />Integer one_I = mapper.readValue("1", Integer.class);<br />Long one_L = mapper.readValue("1", Long.class);<br />Boolean bool = mapper.readValue("false", Boolean.class);<br />String str = mapper.readValue("\"abc\"", String.class);<br /><br />// throws JsonMappingException<br />String anotherStr = <br /> mapper.readValue("[\"abc\"]", String.class);</pre><b>Comparison Notes:</b> With the exception of the last line, the results of the Jackson solution are identical with that of the Gson solution. Where the last line exposes a difference, is in that Gson will automagically transform a single-component JSON array with a primitive value into just the primitive value, unwrapping and discarding the array construct. That Gson has this ability and provides such a simple configuration option (by just specifying a primitive type) to use it can be useful. For Jackson, I logged <a target="_blank" href="http://jira.codehaus.org/browse/JACKSON-592">issue 592</a> to request an enhancement for a similar auto-unwrapping feature. Otherwise, the current possible solutions with Jackson involve a few lines of "manual" JSON deserialization processing, or just using a collection type (such as an array) on the Java side, which both Gson and Jackson can very simply do.<pre name="code" class="java">// With Gson<br />String[] anotherStr = <br /> gson.fromJson("[\"abc\"]", String[].class);<br /><br />// With Jackson<br />String[] anotherStr = <br /> mapper.readValue("[\"abc\"]", String[].class);</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE for primitive value handling</li><li>+1 Gson for simple unwrapping single-component arrays</li></ul><br /><a name="TOC-Exception-Handling"><h3>Exception Handling</h3></a><br />Worth noting with the examples so far and many of the following examples is that Jackson requires explicit handling of the possible checked JsonMappingException and JsonGenerationException exceptions (either by throwing or catching them), but Gson does not have any such required checked exception handling. Instead, Gson will just throw an unchecked JsonParseException, which might catch a naive programmer by surprise, causing unexpected runtime failures (which can lead to other problems).<br /><br />Gson also hides possible IOExceptions by wrapping them in unchecked exceptions, sometimes as JsonIOException and sometimes simply as RuntimeException.<pre name="code" class="java">// From Gson.toJson(JsonElement, Appendable):<br />...<br />catch (IOException e) {<br /> throw new RuntimeException(e);<br />}<br /><br />// From Gson.toJson(JsonElement, JsonWriter)<br />...<br />catch (IOException e) {<br /> throw new JsonIOException(e);<br />}</pre><b>Comparison Rating:</b> +1 Jackson* for using checked exceptions for errors during JSON parsing and generation<br /><br />*Yes, I am expressing a programming style preference. Given all of the <a target="_blank" href="http://c2.com/cgi/wiki?CheckedExceptionsAreOfDubiousValue">Checked Exceptions Are Of Dubious Value</a> arguments, I acknowledge no significant benefit in not requiring the programmer to be aware of and to handle the possibility of unexpected JSON input and/or incorrect data-binding configuration. As already expressed, I see this as possibly detrimental.<br /><br /><a name="TOC-Object-Examples"><h3>Object Examples</h3></a><br />Gson makes it very convenient to bind a Java object field with a JSON element, since it will use any field by default, even if it is private. By default, Jackson requires something more than just the existence of a field, in order to bind a JSON element to it. It requires the field to be public, or to have getter/setter methods. Alternatively, the access visibility rules the ObjectMapper uses can be altered to allow private field use, or the class defining the field can be so annotated. Following are examples of all four Jackson-based solutions.<br /><br /><b>The Gson Code:</b> See <a target="gson_guide" href="http://sites.google.com/site/gson/gson-user-guide#TOC-Object-Examples">relevant section in the Gson user guide</a>.<br /><br /><b>The comparable Jackson Code:</b><pre name="code" class="java">// with public fields<br />class BagOfPrimitives<br />{<br /> public int value1 = 1;<br /> public String value2 = "abc";<br /> public transient int value3 = 3;<br />}<br />// ...<br />BagOfPrimitives obj = new BagOfPrimitives();<br />ObjectMapper mapper = new ObjectMapper();<br /><br />// Serialization<br />String json = mapper.writeValueAsString(obj);<br />System.out.println(json);<br /><br />// Deserialization<br />BagOfPrimitives obj2 = <br /> mapper.readValue(json, BagOfPrimitives.class);<br />System.out.println(mapper.writeValueAsString(obj2));</pre><pre name="code" class="java">// with getters/setters<br />class BagOfPrimitives<br />{<br /> private int value1 = 1;<br /> private String value2 = "abc";<br /> private transient int value3 = 3;<br /> <br /> public void setValue1(int v) {value1 = v;}<br /> public int getValue1() {return value1;}<br /> public void setValue2(String v) {value2 = v;}<br /> public String getValue2() {return value2;}<br />}<br />// ...<br />BagOfPrimitives obj = new BagOfPrimitives();<br />ObjectMapper mapper = new ObjectMapper();<br /><br />// Serialization<br />String json = mapper.writeValueAsString(obj);<br />System.out.println(json);<br /><br />// Deserialization<br />BagOfPrimitives obj2 = <br /> mapper.readValue(json, BagOfPrimitives.class);<br />System.out.println(mapper.writeValueAsString(obj2));</pre><pre name="code" class="java">// with ObjectMapper's field visibility access rule modified<br />class BagOfPrimitives<br />{<br /> private int value1 = 1;<br /> private String value2 = "abc";<br /> private transient int value3 = 3;<br />}<br />// ...<br />BagOfPrimitives obj = new BagOfPrimitives();<br />ObjectMapper mapper = new ObjectMapper();<br />mapper.setVisibilityChecker(<br /> mapper.getVisibilityChecker()<br /> .withFieldVisibility(Visibility.ANY));<br /><br />// Serialization<br />String json = mapper.writeValueAsString(obj);<br />System.out.println(json);<br /><br />// Deserialization<br />BagOfPrimitives obj2 = <br /> mapper.readValue(json, BagOfPrimitives.class);<br />System.out.println(mapper.writeValueAsString(obj2));</pre><pre name="code" class="java">// with the @JsonAutoDetect annotation<br />@JsonAutoDetect(fieldVisibility=Visibility.ANY)<br />class BagOfPrimitives<br />{<br /> private int value1 = 1;<br /> private String value2 = "abc";<br /> private transient int value3 = 3;<br />}<br />// ...<br />BagOfPrimitives obj = new BagOfPrimitives();<br />ObjectMapper mapper = new ObjectMapper();<br /><br />// Serialization<br />String json = mapper.writeValueAsString(obj);<br />System.out.println(json);<br /><br />// Deserialization<br />BagOfPrimitives obj2 = <br /> mapper.readValue(json, BagOfPrimitives.class);<br />System.out.println(mapper.writeValueAsString(obj2));</pre><b>Additional Code Notes:</b><ul><li>Yet another approach available with Jackson to solve this problem is with <a target="_blank" href="http://wiki.fasterxml.com/JacksonMixInAnnotations">Mix-In Annotations</a>. Use of mix-in annotations is an option for all Jackson annotations.</li><li>To enhance Jackson to provide a more terse API for configuring member visibility rules, I logged <a target="_blank" href="https://jira.codehaus.org/browse/JACKSON-595">issue 595</a>. Please don't hesitate to vote for its implementation.</li><li>Gson cannot be configured to use getters/setters instead of direct field access, and <a target="_blank" href="https://groups.google.com/forum/#!topic/google-gson/4G6Lv9PghUY">the latest words on the subject</a> are "[t]he prospects of such a feature making [it] into Gson are fairly low..."</li></ul><b>Additional Comparison Notes on Default Behaviors:</b> Both Gson and Jackson skip transient fields.<br /><br />Before going further through the Gson user guide, comparing Gson use with Jackson, I want to point out a significant difference between these two libraries: <br /><br />Jackson can deserialize any valid JSON object into a <code>Map</code> in one simple line of code. Gson cannot. And the <code>Map</code> generated by Jackson is composed of type-appropriate, standard Java library value objects.<br /><br />Jackson can similarly simply turn any JSON array into a List or Object[], composed of type-appropriate, standard Java library value objects. Gson cannot.<br /><br />So, given any JSON object, for folks that don't want to go through the details of designing a strongly typed Java data structure to bind the JSON data to, and ensuring that all of the data-binding is occurring correctly, they can just do this.<pre name="code" class="java">Map map = mapper.readValue(json, Map.class);</pre>The following code demonstrates this feature and outlines what the deserialized <code>Map</code> is composed of.<pre name="code" class="java">// input: {"a":"A","b":{"c":"C","d":["D","E","F"]}}<br />String json = "{\"a\":\"A\",\"b\":{\"c\":\"C\",\"d\":" +<br /> "[\"D\",\"E\",\"F\"]}}";<br /><br />ObjectMapper mapper = new ObjectMapper();<br />Object foo = mapper.readValue(json, Object.class);<br />System.out.println(foo);<br />// output: {a=A, b={c=C, d=[D, E, F]}}<br />System.out.println(mapper.writeValueAsString(foo));<br />// output: {"a":"A","b":{"c":"C","d":["D","E","F"]}}<br />System.out.println(foo.getClass());<br />// output: class java.util.LinkedHashMap<br /><br />// What specifically is in the fooMap?<br />Map<String, Object> fooMap = (Map) foo;<br />for (Entry entry : fooMap.entrySet())<br />{<br /> System.out.printf("key: %s, value: %s (%s)\n", <br /> entry.getKey(), entry.getValue(), <br /> entry.getValue().getClass());<br />}<br />// output:<br />// key: a, value: A (class java.lang.String)<br />// key: b, <br />// value: {c=C, d=[D, E, F]} (class java.util.LinkedHashMap)<br /><br />// And what is in the b map?<br />Map<String, Object> bMap = (Map) fooMap.get("b");<br />System.out.println(bMap);<br />// output: {c=C, d=[D, E, F]}<br />for (Entry entry: bMap.entrySet())<br />{<br /> System.out.printf("key: %s, value: %s (%s)\n", <br /> entry.getKey(), entry.getValue(), <br /> entry.getValue().getClass());<br />}<br />// output:<br />// key: c, value: C (class java.lang.String)<br />// key: d, value: [D, E, F] (class java.util.ArrayList)<br /><br />// Trying this with Gson...<br />Gson gson = new Gson();<br /><br />gson.fromJson(json, Object.class);<br />// throws JsonParseException: Type information is unavailable<br /><br />gson.fromJson(json, Map.class);<br />// throws JsonParseException: The JsonDeserializer <br />// MapTypeAdapter failed to deserialize json<br /><br />Type mapOfStringObject = <br /> new TypeToken<Map<String, Object>>() {}.getType();<br />gson.fromJson(json, mapOfStringObject);<br />// throws JsonParseException: Type information is unavailable</pre><b>The comparable Gson Code to turn any JSON object into a Map:</b><pre name="code" class="java">// Gson's closest equivalent ability<br />JsonElement je = new JsonParser().parse(json);<br />JsonObject jo = je.getAsJsonObject();<br />System.out.println(jo);<br />// output: {"a":"A","b":{"c":"C","d":["D","E","F"]}}<br /><br />// The JsonObject can be used similarly as a Map is used, but <br />// it's not a Map, and it is composed of Gson API components.<br />// To turn it into a real Map, without Gson components...<br />Map<String, Object> map = createMapFromJsonObject(jo);<br />System.out.println(map);<br />// output: {b={d=[D, E, F], c=C}, a=A}<br /><br />// ...<br /><br />static Map<String, Object> createMapFromJsonObject(<br /> JsonObject jo)<br />{<br /> Map<String, Object> map = new HashMap<String, Object>();<br /> for (Entry<String, JsonElement> entry : jo.entrySet())<br /> {<br /> String key = entry.getKey();<br /> JsonElement value = entry.getValue();<br /> map.put(key, getValueFromJsonElement(value));<br /> }<br /> return map;<br />}<br /><br />static Object getValueFromJsonElement(JsonElement je)<br />{<br /> if (je.isJsonObject())<br /> {<br /> return createMapFromJsonObject(je.getAsJsonObject());<br /> }<br /> else if (je.isJsonArray())<br /> {<br /> JsonArray array = je.getAsJsonArray();<br /> List<Object> list = new ArrayList<Object>(array.size());<br /> for (JsonElement element : array)<br /> {<br /> list.add(getValueFromJsonElement(element));<br /> }<br /> return list;<br /> }<br /> else if (je.isJsonNull())<br /> {<br /> return null;<br /> }<br /> else // must be primitive<br /> {<br /> JsonPrimitive p = je.getAsJsonPrimitive();<br /> if (p.isBoolean()) return p.getAsBoolean();<br /> if (p.isString()) return p.getAsString();<br /> // else p is number, but don't know what kind<br /> String s = p.getAsString();<br /> try<br /> {<br /> return new BigInteger(s);<br /> }<br /> catch (NumberFormatException e)<br /> {<br /> // must be a decimal<br /> return new BigDecimal(s);<br /> }<br /> }<br />}</pre>With Gson, similar processing using custom deserializers is possible.<br /><br /><b>Comparison Rating:</b> +1 Jackson for very simple deserialization of any JSON object to a <code>Map</code>, and any JSON array to a <code>List</code>, composed of type-appropriate, standard Java library value objects<br /><br />Continuing with the Gson user guide...<br /><br />Handling of null reference fields and null JSON values sometimes differs and is sometimes the same between the two APIs.<br /><br />By default, during serialization, Gson skips null reference fields, and they do not appear in the JSON. Jackson includes them.<pre name="code" class="java">class BagOfPrimitives<br />{<br /> public int value1 = 1;<br /> public String value2 = null;<br /> public transient int value3 = 3;<br />}<br /><br />public class NullHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> BagOfPrimitives obj = new BagOfPrimitives();<br /> Gson gson = new Gson();<br /> System.out.println(gson.toJson(obj));<br /> // output: {"value1":1}<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> System.out.println(mapper.writeValueAsString(obj));<br /> // output: {"value1":1,"value2":null}<br /> }<br />}</pre>During deserialization, both libraries use a JSON element null value to set the associated Java field accordingly, as a null reference.<pre name="code" class="java">class BagOfPrimitives<br />{<br /> public int value1 = 1;<br /> public String value2 = "abc";<br /> public transient int value3 = 3;<br />}<br /><br />public class NullHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"value1":100,"value2":null}<br /> String jsonInput = "{\"value1\":100,\"value2\":null}";<br /><br /> Gson gson = new Gson();<br /> BagOfPrimitives obj1 =<br /> gson.fromJson(jsonInput, BagOfPrimitives.class);<br /> System.out.println(gson.toJson(obj1));<br /> // output: {"value1":100}<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> BagOfPrimitives obj2 =<br /> mapper.readValue(jsonInput, BagOfPrimitives.class);<br /> System.out.println(mapper.writeValueAsString(obj2));<br /> // output: {"value1":100,"value2":null}<br /> }<br />}</pre>During deserialization, if the JSON does not contain an element to match a field defined in the Java class, both Gson and Jackson do not set the relevant reference field to null, or any relevant primitive field to its default value. To clarify, if the field is assigned a value during normal object creation, the value is not replaced with anything by Gson or Jackson during deserialization. (This somewhat contradicts the statement in the Gson user guide that, "While deserialization, a missing entry in JSON results in setting the corresponding field in the object to null.")<pre name="code" class="java">class BagOfPrimitives<br />{<br /> public int value1 = 1;<br /> public String value2 = "abc";<br /> public transient int value3 = 3;<br />}<br /><br />public class NullHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"value1":100}<br /> String jsonInput = "{\"value1\":100}";<br /><br /> Gson gson = new Gson();<br /> BagOfPrimitives obj1 =<br /> gson.fromJson(jsonInput, BagOfPrimitives.class);<br /> System.out.println(gson.toJson(obj1));<br /> // output: {"value1":100,"value2":"abc"}<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> BagOfPrimitives obj2 =<br /> mapper.readValue(jsonInput, BagOfPrimitives.class);<br /> System.out.println(mapper.writeValueAsString(obj2));<br /> // output: {"value1":100,"value2":"abc"}<br /> }<br />}</pre>Additional information on null handling is covered later, in the "Null Object Support" section of the Gson user guide, reviewed in part 4 of this series.<br /><br />Both libraries skip synthetic fields.<pre name="code" class="java">class BagOfPrimitives<br />{<br /> public int value1 = 1;<br /> public String value2 = "abc";<br /> public transient int value3 = 3;<br /> <br /> public class ThisCausesSyntheticFieldCreation{}<br />}<br /><br />public class SyntheticFieldHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> Gson gson = new Gson();<br /> BagOfPrimitives obj = new BagOfPrimitives();<br /> System.out.println(gson.toJson(obj));<br /> // output: {"value1":100,"value2":"abc"}<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> System.out.println(mapper.writeValueAsString(obj));<br /> // output: {"value1":100,"value2":"abc"}<br /> }<br />}</pre><b>Comparison Ratings:</b><ul><li>COMPARABLE** for direct field access handling</li><li>+1 Jackson for providing optional use of getters and setters</li><li>COMPARABLE for default handling of null</li><li>COMPARABLE for synthetic field handling</li></ul>**That Jackson requires an explicit configuration to enable private field access is inconsequential, since the configuration options are very simple.<br /><br /><a name="TOC-Missing-Element-Handling"><h3>Object Examples - Missing Element Handling</h3></a><br />Gson and Jackson both deserialize without problems when the JSON does not have an element to populate a Java field. They also do not erase whatever default value is otherwise provided for the field.<pre name="code" class="java">public class MissingElementHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"name":"Spike"}<br /> String jsonInput = "{\"name\":\"Spike\"}";<br /> <br /> Gson gson = new Gson();<br /> Dog dog1 = gson.fromJson(jsonInput, Dog.class);<br /> System.out.println(gson.toJson(dog1));<br /> // output: {"name":"Spike","age":-1}<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> Dog dog2 = mapper.readValue(jsonInput, Dog.class);<br /> System.out.println(mapper.writeValueAsString(dog2));<br /> // output: {"name":"Spike","age":-1}<br /> }<br />}<br /><br />class Dog<br />{<br /> public String name = "UNNAMED";<br /> public int age = -1;<br />}</pre><b>Comparison Rating:</b> COMPARABLE<br /><br /><a name="TOC-Extra-Element-Handling"><h3>Object Examples - Extra Element Handling</h3></a><br />By default, Gson just ignores extra JSON elements that do not have matching Java fields. Jackson instead throws an UnrecognizedPropertyException, but offers two simple configuration options to enable extra element handling: the class can be annotated with @JsonIgnoreProperties(ignoreUnknown=true), or the ObjectMapper can be directly configured to not FAIL_ON_UNKNOWN_PROPERTIES. (Jackson also offers other ways to handle extra JSON elements. See <a target="_blank" href="http://wiki.fasterxml.com/JacksonHowToIgnoreUnknown">http://wiki.fasterxml.com/JacksonHowToIgnoreUnknown</a> for details.)<pre name="code" class="java">public class ExtraElementHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"name":"Spike","breed":"Collie"}<br /> String json = "{\"name\":\"Spike\",\"breed\":\"Collie\"}";<br /> <br /> Gson gson = new Gson();<br /> Dog dog1 = gson.fromJson(json, Dog.class);<br /> System.out.println(gson.toJson(dog1));<br /> // output: {"name":"Spike"}<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.configure(<br /> DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,<br /> false);<br /><br /> Dog dog2 = mapper.readValue(json, Dog.class);<br /> System.out.println(mapper.writeValueAsString(dog2));<br /> // output: {"name":"Spike"}<br /> }<br />}<br /><br />// alternative to configuring ObjectMapper<br />// @JsonIgnoreProperties(ignoreUnknown=true)<br />class Dog<br />{<br /> public String name;<br />}</pre><b>Comparison Rating:</b> COMPARABLE***<br /><br />***That Jackson requires an explicit configuration to enable skipping extra JSON elements is inconsequential, since the configuration options are very simple.<br /><br /><a name="TOC-Nested-Classes-including-Inner-Clas"><h3>Static Nested Classes</h3></a><br />Both Gson and Jackson serialize and deserialize instances of static nested classes very simply.<pre name="code" class="java">class A<br />{<br /> public static class B {public String b;}<br />}<br /><br />public class StaticNestedClassHandling<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"b":"123"}<br /> String jsonInput = "{\"b\":\"123\"}";<br /> <br /> Gson gson = new Gson();<br /> A.B b1 = gson.fromJson(jsonInput, A.B.class);<br /> System.out.println(gson.toJson(b1));<br /> // output: {"b":"123"}<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> A.B b2 = mapper.readValue(jsonInput, A.B.class);<br /> System.out.println(mapper.writeValueAsString(b2));<br /> // output: {"b":"123"}<br /> }<br />}</pre><b>Comparison Rating:</b> COMPARABLE<br /><br /><a name="TOC-Inner-Classes-Serialization"><h3>(Non-Static) Inner Classes - Serialization</h3></a><br />Gson and Jackson serialize instances of inner classes similarly.<pre name="code" class="java">class A<br />{<br /> public String a = "AAA";<br /> public B ab = new B();<br /> public class B {public String b = "BBB";}<br />}<br /><br />public class InnerClassSerialization<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> A a = new A();<br /> A.B ab = a.new B();<br /> Gson gson = new Gson();<br /> System.out.println(gson.toJson(a));<br /> // output: {"a":"AAA","ab":{"b":"BBB"}}<br /> System.out.println(gson.toJson(ab));<br /> // output: {"b":"BBB"}<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> System.out.println(mapper.writeValueAsString(a));<br /> // output: {"a":"AAA","ab":{"b":"BBB"}}<br /> System.out.println(mapper.writeValueAsString(ab));<br /> // output: {"b":"BBB"}<br /> }<br />}</pre><b>Comparison Rating:</b> COMPARABLE<br /><br /><a name="TOC-Anonymous-Inner-Classes-Ser"><h3>Anonymous Inner Classes - Serialization</h3></a><br />Anonymous inner class serialization ability differs between Gson and Jackson. Both libraries serialize an object with a reference to an anonymous inner class, including the values of all of the attributes, but only Jackson serializes the values given a direct reference to an anonymous inner class.<pre name="code" class="java">class A<br />{<br /> public String a = "AAA";<br /> public B ab = new B() {public String b2 = "123";};<br />}<br /><br />class B<br />{<br /> public String b = "BBB";<br />}<br /><br />public class AnonymousInnerClassSerialization<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> A a = new A();<br /> <br /> Gson gson = new Gson();<br /> System.out.println(gson.toJson(a));<br /> // output: {"a":"AAA","ab":{"b2":"123","b":"BBB"}}<br /> <br /> System.out.println(gson.toJson(a.ab));<br /> // output: EMPTY STRING<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> System.out.println(mapper.writeValueAsString(a));<br /> // output: {"a":"AAA","ab":{"b":"BBB","b2":"123"}}<br /> <br /> System.out.println(mapper.writeValueAsString(a.ab));<br /> // output: {"b":"BBB","b2":"123"}<br /> }<br />}</pre><b>Comparison Rating:</b> +1 Jackson for successfully serializing from an anonymous inner class reference<br /><br /><a name="TOC-Inner-Classes-Deserialization"><h3>(Non-Static) Inner Classes - Deserialization</h3></a><br />Contradicting the explanation in the Gson user guide that instances of inner class cannot be deserialized without a custom InstanceCreator, the latest release of Gson actually deserializes to inner classes just fine. <a target="_blank" href="http://www.cowtowncoder.com/blog/archives/2010/08/entry_411.html">Jackson fails to perform similar deserialization</a> without custom handling.<pre name="code" class="java">class A<br />{<br /> public String a = "AAA";<br /> public B ab = new B();<br /> public class B {public String b = "BBB";}<br />}<br /><br />public class InnerClassDeserialization<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"a":"AAA","ab":{"b":"BBB"}}<br /> String jsonInputA = <br /> "{\"a\":\"AAA\",\"ab\":{\"b\":\"BBB\"}}";<br /> <br /> // input: {"b":"BBB"}<br /> String jsonInputB = "{\"b\":\"BBB\"}";<br /> <br /> Gson gson = new Gson();<br /> A a1 = gson.fromJson(jsonInputA, A.class);<br /> System.out.println(gson.toJson(a1));<br /> // output: {"a":"AAA","ab":{"b":"BBB"}}<br /> A.B b1 = gson.fromJson(jsonInputB, A.B.class);<br /> System.out.println(gson.toJson(b1));<br /> // output: {"b":"BBB"}<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> <br /> // throws JsonMappingException<br /> // A a2 = mapper.readValue(jsonInputA, A.class);<br /><br /> // throws JsonMappingException<br /> // A.B b2 = mapper.readValue(jsonInputB, A.B.class);<br /> }<br />}</pre><b>A comparable (and fragile) Jackson Solution:</b><pre name="code" class="java">class A<br />{<br /> public String a = "AAA";<br /> public B ab = new B();<br /> public class B {public String b = "BBB";}<br />}<br /><br />public class InnerClassDeserialization<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // input: {"a":"AAA","ab":{"b":"BBB"}}<br /> String jsonInputA = <br /> "{\"a\":\"AAA\",\"ab\":{\"b\":\"BBB\"}}";<br /> <br /> // input: {"b":"BBB"}<br /> String jsonInputB = "{\"b\":\"BBB\"}";<br /> <br /> Gson gson = new Gson();<br /> A a1 = gson.fromJson(jsonInputA, A.class);<br /> System.out.println(gson.toJson(a1));<br /> // output: {"a":"AAA","ab":{"b":"BBB"}}<br /> A.B ab1 = gson.fromJson(jsonInputB, A.B.class);<br /> System.out.println(gson.toJson(ab1));<br /> // output: {"b":"BBB"}<br /><br /> ObjectMapper mapper = new ObjectMapper();<br /> <br /> A a2 = readAValue(mapper, jsonInputA);<br /> System.out.println(mapper.writeValueAsString(a2));<br /> // output: {"a":"AAA","ab":{"b":"BBB"}}<br /><br /> A.B ab2 = readAbValue(mapper, jsonInputB);<br /> System.out.println(mapper.writeValueAsString(ab2));<br /> // output: {"b":"BBB"}<br /> }<br /> <br /> static A readAValue(ObjectMapper mapper, String json)<br /> throws Exception<br /> {<br /> JsonNode node = mapper.readTree(json);<br /> A a = new A();<br /> a.a = node.get("a").getTextValue();<br /> a.ab = a.new B();<br /> a.ab.b = node.get("ab").get("b").getTextValue();<br /> return a;<br /> }<br /> <br /> static A.B readAbValue(ObjectMapper mapper, String json)<br /> throws Exception<br /> {<br /> JsonNode node = mapper.readTree(json);<br /> A a = new A();<br /> a.ab = a.new B();<br /> a.ab.b = node.get("b").getTextValue();<br /> return a.ab;<br /> }<br />}</pre><b>Additional Notes:</b> To enhance Jackson to easily deserialize to inner class instances, I logged <a target="_blank" href="https://jira.codehaus.org/browse/JACKSON-594">issue 594</a>. Please don't hesitate to vote for its delivery.<br /><br /><b>Comparison Rating:</b> +1 Gson for simple deserialization of inner classes (even if the docs say it cannot do it)<br /><br /><h3>Anonymous Inner Classes - Deserialization</h3><br />Neither library successfully deserializes to anonymous inner classes, without "manual" deserialization processing. Gson skips and clears any fields defined in the anonymous inner class, and Jackson throws an exception.<pre name="code" class="java">class A<br />{<br /> public String a = "AAA";<br /> public B ab = new B() {public String b2 = "123";};<br />}<br /><br />class B<br />{<br /> public String b = "BBB";<br />}<br /><br />public class AnonymousInnerClassDeserialization<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> // {"a":"a value","ab":{"b":"b value","b2":"b2 value"}}<br /> String json1 = "{\"a\":\"a value\",\"ab\":" +<br /> "{\"b\":\"b value\",\"b2\":\"b2 value\"}}";<br /> <br /> // {"b":"b value","b2":"b2 value"}<br /> String json2 = "{\"b\":\"b value\",\"b2\":\"b2 value\"}";<br /> <br /> Gson gson = new Gson();<br /> ObjectMapper mapper = new ObjectMapper();<br /> <br /> A a = gson.fromJson(json1, A.class);<br /> System.out.println(gson.toJson(a));<br /> // output: {"a":"a value","ab":{"b":"b value"}}<br /> // b2 field value assignment skipped<br /> // and cleared default "123" assignment<br /> <br /> B b = gson.fromJson(json2, B.class);<br /> System.out.println(mapper.writeValueAsString(b));<br /> // using Jackson's ObjectMapper, since Gson does not<br /> // serialize fields introduced in anonymous inner<br /> // class definitions<br /> // output: {"b":"b value"}<br /> // b2 cleared and skipped as above<br /> <br /> // A a2 = mapper.readValue(json1, A.class);<br /> // throws Exception<br /> <br /> // B b2 = mapper.readValue(json2, B.class);<br /> // throws Exception<br /> }<br />}</pre><b>Comparison Rating:</b> COMPARABLE -- They both do not succeed.<br /><br /><a href="http://programmerbruce.blogspot.com/2011/06/gson-v-jackson-part-2.html">Continue to part 2...</a><br /><br /><h2>References And Resources:</h2><ul><li><a target="_blank" href="http://code.google.com/p/google-gson">Gson Project Home</a></li><li><a target="_blank" href="http://jackson.codehaus.org">Jackson Project Home</a></li><li><a target="_blank" href="https://groups.google.com/forum/#!forum/google-gson">Gson User Group Mailing List</a></li><li><a target="_blank" href="http://jackson-users.ning.com">Jackson JSON User Group</a></li><li><a target="_blank" href="http://code.google.com/p/google-gson/source/checkout">The Gson Source Code</a></li><li><a target="_blank" href="http://svn.codehaus.org/jackson/">The Jackson Source Code</a></li></ul>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com4tag:blogger.com,1999:blog-5325426185501267599.post-52683790128201693112011-05-25T17:04:00.025-05:002013-08-10T23:26:44.602-05:00Deserialize JSON with Jackson into Polymorphic Types - A Complete Example<table border="0"><tbody><tr><td><h3>Jackson API Versions Note</h3>The following code examples were written using Jackson 1.7.x or 1.8.x. They probably work as-is with Jackson 1.9.x, but Jackson 2.0 introduced API changes significant enough that this code does not compile with it. On initial review, it appears that package names changed, and at least one method signature changed or was moved or was renamed or no longer exists. In the near future, I may post updated code examples that use Jackson 2.x.<br /><br /><h3>tl;dnr</h3>Skip to the fourth, fifth, and sixth examples.<br /><br /><h3>Why This Post Exists</h3>While recently answering a question on StackOverflow.com about deserialization of JSON into polymorhpic types in Java, I couldn't find a simple and complete example using <a target="_blank" href="http://jackson.codehaus.org">Jackson</a>. At the time of this writing, <a target="_blank" href="http://wiki.fasterxml.com/JacksonPolymorphicDeserialization">the official documentation on the subject</a> describes aspects of how to do this, but it doesn't have a full example. <a target="_blank" href="http://www.google.com/search?q=jackson+polymorphic">Searching</a> <a target="_blank" href="http://jackson-users.ning.com/main/search/search?q=polymorphic">other</a> <a target="_blank" href="http://stackoverflow.com/search?q=%5Bjackson%5D%20polymorphic">resources</a> also didn't turn up any complete examples.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/nVKBZ" alt="QR Code Link To This Article" border="0" />http://goo.gl/nVKBZ</div></td></tr></tbody></table><br />Following are deserialization examples with Jackson that build up to a complete example of populating a polymorphic data structure in Java. The first three examples do not involve Polymorphism. Polymorphism is introduced in the fourth example.<br /><a name='more'></a><br />Consult <a target="_blank" href="http://wiki.fasterxml.com/JacksonJavaDocs">the Jackson API documentation</a> for explanations of each component.<br /><br /><h3>Example 1: Simple Object Deserialization and Serialization</h3><pre name="code" class="java">// input and output:<br />// {"type":"dog","name":"Spike"}<br /><br />import org.codehaus.jackson.map.ObjectMapper;<br /><br />public class Foo<br />{<br /> static String jsonInput = <br /> "{\"type\":\"dog\",\"name\":\"Spike\"}";<br /><br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> Animal animal = mapper.readValue(jsonInput, Animal.class);<br /> System.out.println(mapper.writeValueAsString(animal));<br /> }<br />}<br /><br />class Animal<br />{<br /> public String type;<br /> public String name;<br />}</pre><br /><h3>Example 2: Simple Collection Deserialization and Serialization</h3><pre name="code" class="java">// input and output:<br />// [<br />// {"type":"dog","name":"Spike"},<br />// {"type":"cat","name":"Fluffy"}<br />// ]<br /><br />import java.io.File;<br />import java.util.Collection;<br /><br />import org.codehaus.jackson.map.ObjectMapper;<br />import org.codehaus.jackson.map.type.TypeFactory;<br />import org.codehaus.jackson.type.TypeReference;<br /><br />public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> Collection<Animal> animals =<br /> mapper.readValue(new File("input_2.json"),<br /> new TypeReference<Collection<Animal>>() {});<br /> System.out.println(mapper.writeValueAsString(animals));<br /> // or<br /> Collection<Animal> animals2 =<br /> mapper.readValue(new File("input_2.json"),<br /> TypeFactory.defaultInstance().constructParametricType(<br /> Collection.class, Animal.class));<br /> System.out.println(mapper.writeValueAsString(animals2));<br /> }<br />}<br /><br />class Animal<br />{<br /> public String type;<br /> public String name;<br />}</pre><br /><h3>Example 3: Simple Deserialization/Serialization To/From Container Object With Collection</h3><pre name="code" class="java">// input and output:<br />// {<br />// "animals":<br />// [<br />// {"type":"dog","name":"Spike"},<br />// {"type":"cat","name":"Fluffy"}<br />// ]<br />// }<br /><br />import java.io.File;<br />import java.util.Collection;<br /><br />import org.codehaus.jackson.map.ObjectMapper;<br /><br />public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> Zoo zoo = <br /> mapper.readValue(new File("input_3.json"), Zoo.class);<br /> System.out.println(mapper.writeValueAsString(zoo));<br /> }<br />}<br /><br />class Zoo<br />{<br /> public Collection<Animal> animals;<br />}<br /><br />class Animal<br />{<br /> public String type;<br /> public String name;<br />}</pre><br /><h3>Example 4: Simple Deserialization/Serialization To/From Container Object With Polymorphic Collection</h3><pre name="code" class="java">// input and output:<br />// {<br />// "animals":<br />// [<br />// {"type":"dog","name":"Spike","breed":"mutt",<br />// "leash_color":"red"},<br />// {"type":"cat","name":"Fluffy",<br />// "favorite_toy":"spider ring"}<br />// ]<br />// }<br /><br />import java.io.File;<br />import java.util.Collection;<br /><br />import org.codehaus.jackson.annotate.JsonSubTypes;<br />import org.codehaus.jackson.annotate.JsonSubTypes.Type;<br />import org.codehaus.jackson.annotate.JsonTypeInfo;<br />import org.codehaus.jackson.map.ObjectMapper;<br /><br />public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.setPropertyNamingStrategy(<br /> new CamelCaseNamingStrategy());<br /> Zoo zoo = <br /> mapper.readValue(new File("input_4.json"), Zoo.class);<br /> System.out.println(mapper.writeValueAsString(zoo));<br /> }<br />}<br /><br />class Zoo<br />{<br /> public Collection<Animal> animals;<br />}<br /><br />@JsonTypeInfo(<br /> use = JsonTypeInfo.Id.NAME,<br /> include = JsonTypeInfo.As.PROPERTY,<br /> property = "type")<br />@JsonSubTypes({<br /> @Type(value = Cat.class, name = "cat"),<br /> @Type(value = Dog.class, name = "dog") })<br />abstract class Animal<br />{<br /> public String name;<br />}<br /><br />class Dog extends Animal<br />{<br /> public String breed;<br /> public String leashColor;<br />}<br /><br />class Cat extends Animal<br />{<br /> public String favoriteToy;<br />}</pre><pre name="code" class="java">import org.codehaus.jackson.map.MapperConfig;<br />import org.codehaus.jackson.map.PropertyNamingStrategy;<br />import org.codehaus.jackson.map.introspect.AnnotatedField;<br />import org.codehaus.jackson.map.introspect.AnnotatedMethod;<br /><br />/** <br /> * Converts standard CamelCase field and method names to <br /> * typical JSON field names having all lower case characters <br /> * with an underscore separating different words. For <br /> * example, all of the following are converted to JSON field <br /> * name "some_name": <br /> * <br /> * Java field name "someName" <br /> * Java method name "getSomeName" <br /> * Java method name "setSomeName" <br /> * <br /> * Typical Use: <br /> * <br /> * String jsonString = "{\"foo_name\":\"fubar\"}"; <br /> * ObjectMapper mapper = new ObjectMapper(); <br /> * mapper.setPropertyNamingStrategy( <br /> * new CamelCaseNamingStrategy()); <br /> * Foo foo = mapper.readValue(jsonString, Foo.class); <br /> * System.out.println(mapper.writeValueAsString(foo)); <br /> * // prints {"foo_name":"fubar"} <br /> * <br /> * class Foo <br /> * { <br /> * private String fooName; <br /> * public String getFooName() {return fooName;} <br /> * public void setFooName(String fooName) <br /> * {this.fooName = fooName;} <br /> * } <br /> */<br />public class CamelCaseNamingStrategy<br /> extends PropertyNamingStrategy<br />{<br /> @Override<br /> public String nameForGetterMethod(MapperConfig<?> config,<br /> AnnotatedMethod method, String defaultName)<br /> {<br /> return translate(defaultName);<br /> }<br /><br /> @Override<br /> public String nameForSetterMethod(MapperConfig<?> config,<br /> AnnotatedMethod method, String defaultName)<br /> {<br /> return translate(defaultName);<br /> }<br /><br /> @Override<br /> public String nameForField(MapperConfig<?> config,<br /> AnnotatedField field, String defaultName)<br /> {<br /> return translate(defaultName);<br /> }<br /><br /> private String translate(String defaultName)<br /> {<br /> char[] nameChars = defaultName.toCharArray();<br /> StringBuilder nameTranslated =<br /> new StringBuilder(nameChars.length * 2);<br /> for (char c : nameChars)<br /> {<br /> if (Character.isUpperCase(c))<br /> {<br /> nameTranslated.append("_");<br /> c = Character.toLowerCase(c);<br /> }<br /> nameTranslated.append(c);<br /> }<br /> return nameTranslated.toString();<br /> }<br />}</pre><br /><h3>Example 5: Simple Deserialization/Serialization With <a target="_blank" href="http://wiki.fasterxml.com/JacksonMixInAnnotations">MixIn</a> To/From Container Object With Polymorphic Collection</h3><pre name="code" class="java">// input and output:<br />// {<br />// "animals":<br />// [<br />// {"type":"dog","name":"Spike","breed":"mutt",<br />// "leash_color":"red"},<br />// {"type":"cat","name":"Fluffy",<br />// "favorite_toy":"spider ring"}<br />// ]<br />// }<br /><br />import java.io.File;<br />import java.util.Collection;<br /><br />import org.codehaus.jackson.annotate.JsonSubTypes;<br />import org.codehaus.jackson.annotate.JsonTypeInfo;<br />import org.codehaus.jackson.annotate.JsonSubTypes.Type;<br />import org.codehaus.jackson.map.ObjectMapper;<br /><br />public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.setPropertyNamingStrategy(<br /> new CamelCaseNamingStrategy());<br /> mapper.getDeserializationConfig().addMixInAnnotations(<br /> Animal.class, PolymorphicAnimalMixIn.class);<br /> mapper.getSerializationConfig().addMixInAnnotations(<br /> Animal.class, PolymorphicAnimalMixIn.class);<br /> Zoo zoo = <br /> mapper.readValue(new File("input_5.json"), Zoo.class);<br /> System.out.println(mapper.writeValueAsString(zoo));<br /> }<br />}<br /><br />class Zoo<br />{<br /> public Collection<Animal> animals;<br />}<br /><br />@JsonTypeInfo(<br /> use = JsonTypeInfo.Id.NAME,<br /> include = JsonTypeInfo.As.PROPERTY,<br /> property = "type")<br />@JsonSubTypes({<br /> @Type(value = Cat.class, name = "cat"),<br /> @Type(value = Dog.class, name = "dog") })<br />abstract class PolymorphicAnimalMixIn<br />{<br /> <br />}<br /><br />abstract class Animal<br />{<br /> public String name;<br />}<br /><br />class Dog extends Animal<br />{<br /> public String breed;<br /> public String leashColor;<br />}<br /><br />class Cat extends Animal<br />{<br /> public String favoriteToy;<br />}</pre><br /><h3>Example 6: Simple Deserialization Without Type Element To Container Object With Polymorphic Collection</h3><br />Some real-world JSON APIs have polymorphic type members, but don't include type elements (unlike the JSON in the previous examples). Deserializing such sources into polymorphic collections is a bit more involved. Following is one relatively simple solution. (This example includes subsequent serialization of the deserialized Java structure back to input JSON, but the serialization is relatively uninteresting.)<pre name="code" class="java">// input and output:<br />// {<br />// "animals":<br />// [<br />// {"name":"Spike","breed":"mutt","leash_color":"red"},<br />// {"name":"Fluffy","favorite_toy":"spider ring"},<br />// {"name":"Baldy","wing_span":"6 feet",<br />// "preferred_food":"wild salmon"}<br />// ]<br />// }<br /><br />import java.io.File;<br />import java.io.IOException;<br />import java.util.Collection;<br />import java.util.HashMap;<br />import java.util.Iterator;<br />import java.util.Map;<br />import java.util.Map.Entry;<br /><br />import org.codehaus.jackson.JsonNode;<br />import org.codehaus.jackson.JsonParser;<br />import org.codehaus.jackson.JsonProcessingException;<br />import org.codehaus.jackson.Version;<br />import org.codehaus.jackson.map.DeserializationContext;<br />import org.codehaus.jackson.map.ObjectMapper;<br />import org.codehaus.jackson.map.deser.StdDeserializer;<br />import org.codehaus.jackson.map.module.SimpleModule;<br />import org.codehaus.jackson.node.ObjectNode;<br /><br />import fubar.CamelCaseNamingStrategy;<br /><br />public class Foo<br />{<br /> public static void main(String[] args) throws Exception<br /> {<br /> AnimalDeserializer deserializer = <br /> new AnimalDeserializer();<br /> deserializer.registerAnimal("leash_color", Dog.class);<br /> deserializer.registerAnimal("favorite_toy", Cat.class);<br /> deserializer.registerAnimal("wing_span", Bird.class);<br /> SimpleModule module =<br /> new SimpleModule("PolymorphicAnimalDeserializerModule",<br /> new Version(1, 0, 0, null));<br /> module.addDeserializer(Animal.class, deserializer);<br /> <br /> ObjectMapper mapper = new ObjectMapper();<br /> mapper.setPropertyNamingStrategy(<br /> new CamelCaseNamingStrategy());<br /> mapper.registerModule(module);<br /><br /> Zoo zoo = <br /> mapper.readValue(new File("input_6.json"), Zoo.class);<br /> System.out.println(mapper.writeValueAsString(zoo));<br /> }<br />}<br /><br />class AnimalDeserializer extends StdDeserializer<Animal><br />{<br /> private Map<String, Class<? extends Animal>> registry =<br /> new HashMap<String, Class<? extends Animal>>();<br /><br /> AnimalDeserializer()<br /> {<br /> super(Animal.class);<br /> }<br /><br /> void registerAnimal(String uniqueAttribute,<br /> Class<? extends Animal> animalClass)<br /> {<br /> registry.put(uniqueAttribute, animalClass);<br /> }<br /><br /> @Override<br /> public Animal deserialize(<br /> JsonParser jp, DeserializationContext ctxt) <br /> throws IOException, JsonProcessingException<br /> {<br /> ObjectMapper mapper = (ObjectMapper) jp.getCodec();<br /> ObjectNode root = (ObjectNode) mapper.readTree(jp);<br /> Class<? extends Animal> animalClass = null;<br /> Iterator<Entry<String, JsonNode>> elementsIterator = <br /> root.getFields();<br /> while (elementsIterator.hasNext())<br /> {<br /> Entry<String, JsonNode> element=elementsIterator.next();<br /> String name = element.getKey();<br /> if (registry.containsKey(name))<br /> {<br /> animalClass = registry.get(name);<br /> break;<br /> }<br /> }<br /> if (animalClass == null) return null;<br /> return mapper.readValue(root, animalClass);<br /> }<br />}<br /><br />class Zoo<br />{<br /> public Collection<Animal> animals;<br />}<br /><br />abstract class Animal<br />{<br /> public String name;<br />}<br /><br />class Dog extends Animal<br />{<br /> public String breed;<br /> public String leashColor;<br />}<br /><br />class Cat extends Animal<br />{<br /> public String favoriteToy;<br />}<br /><br />class Bird extends Animal<br />{<br /> public String wingSpan;<br /> public String preferredFood;<br />}</pre>FINIProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com43tag:blogger.com,1999:blog-5325426185501267599.post-37536554027700298702011-05-20T15:58:00.027-05:002011-06-17T17:34:18.260-05:00Fixing GeekYouUp's Battery Widget<table border="0"><tbody><tr><td><h3>tl;dnr</h3>GeekYouUp's Battery Widget for Android can be altered to reduce App Widget display update occurrences by as much as 40-65% (depending on device power use rate), without altering application functionality or otherwise hindering its ability to display the current battery charge level. Battery Widget's constantly running service, necessary for real-time monitoring of battery charge level changes, appears to use negligible resources and does not noticeably prematurely drain battery power.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/mscyj" alt="QR Code Link To This Article" border="0" />http://goo.gl/mscyj</div></td></tr></tbody></table><br />Following are links to the code of the fixed version of Battery Widget, logs of the svn differences showing what was changed from the original code, and a fully-built Android APK, ready to be installed on any Android device. This APK was built with a different package name, a different application name, and a different digital signature than the Battery Widget application by GeekYouUp. It can be installed in parallel with the original -- it does <span style="font-style:italic;">not</span> replace the original Battery Widget application during installation.<ul><li><a target="_blank" href="https://sites.google.com/site/programmerbruce/downloads/batterywidget-svn-trunk-2011.05.17-FIXED-src.zip">batterywidget-svn-trunk-2011.05.17-FIXED-src.zip</a></li><li><a target="_blank" href="https://sites.google.com/site/programmerbruce/downloads/batterywidget-svn-trunk-2011.05.17-DIFFS.zip">batterywidget-svn-trunk-2011.05.17-DIFFS.zip</a></li><li><a target="_blank" href="https://sites.google.com/site/programmerbruce/downloads/batterywidget-svn-trunk-2011.05.17-FIXED.apk">batterywidget-svn-trunk-2011.05.17-FIXED.apk</a></li></ul><br /><h3>For Those With Interest In The Details...</h3><a name='more'></a>GeekYouUp's Battery Widget is a popular Android application with <a target="_blank" href="http://stackoverflow.com/questions/6134590/setlistadapter-problem-i-dont-know-how-this-works-at-all/6134733#comment-7120669">over 4.2 million downloads</a>. Unfortunately, it suffers from a few bugs, including unnecessarily (and ironically) using device battery power. Following is a brief description of GeekYouUp's Battery Widget functionality, problems, and proposed solutions. This information applies both to the current 1.6.7 version of Battery Widget available from the Android Market, and to the latest changes available in the application's source code repository (as of 2011.05.10), unless otherwise noted. Richard Hyndman, the developer behind GeekYouUp, was informed of these problems and of these proposed fixes in comments in <a target="_blank" href="http://code.google.com/p/batterywidget/issues/list">the project's issue tracker</a> and through <a target="_blank" href="https://twitter.com/#%21/geekyouup">Twitter</a>.<br /><br /><h3>Observed Application Functionality</h3>GeekYouUp's Battery Widget is an Android application that displays the current battery charge level in a home screen App Widget, and provides quick access to toggle and adjust features that are major consumers of battery power, including display brightness, GPS, Wi-Fi, and Bluetooth settings. (The more a wireless service is used or the brighter the display is, the more battery power the device will consume.)<br /><br /><div style="text-align: center;"><img style="display:block; margin:0px auto 10px; text-align:center;width: 76px; height: 86px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7YbhMLuqk-vO9QfaK8SOfUH6ONw1B6PWGfpf2T_B1J78GylwLEe0XSgg4f0A2u_tKXXtje6D9RapNKkyAdtGvswP2WALKQRKYWo0xDbqktzZ2MQm7JWhXrD1WHAWF5N7YDjiR63OCDa0/s1600/Battery_Widget_by_GeekYouUp.png" alt="Battery Widget by GeekYouUp" id="BLOGGER_PHOTO_ID_5608906594537007778" border="0" />Battery Widget by GeekYouUp</div><br />Clicking on the App Widget instance launches an Activity (labeled "Settings" in the application code) that presents the user with five buttons: Battery, Display, GPS, Wifi, BT.<br /><br /><div style="text-align: center;"><img style="display:block; margin:0px auto 10px; text-align:center; width: 198px; height: 308px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXLnnN-Ry6-h_rURMjBh9p1hqG_DQw3yyntKNfsP2IJUP1Rqk_295nn1aiGiQakFYO-mdL-w02FC5Lp5xnhaASLL7U9jZi8c8cXJRNmLvEFxU5Rc4xw0MrhuI9AeFXMof_Rk9jGMlaLz4/s1600/Battery_Widget_Settings.png" alt="Battery Widget Settings" id="BLOGGER_PHOTO_ID_5608908369347596850" border="0" />Battery Widget Settings</div><br />The "Battery" button launches Android's built-in "Battery use" screen, on devices that provide this feature. The "Battery use" screen lists the top consumers of battery power since the device was last turned on or charged. (Users can get to this same "Battery use" screen using the Settings application, and navigating to "About Phone" and then "Battery use".)<br /><br />The "Display" button launches Android's built-in "Display settings" screen. (On some versions of Android, this is the "Sound & Display settings" or "Sound & display" screen.)<br /><br />The "GPS" button launches the "Security & location settings" screen. (On some versions of Android, this is the "Security & Location" or "Location & security settings" screen.)<br /><br />The "Wifi" button attempts to toggle the Wi-Fi service - turning it on if it were off, and turning it off if it were on - and displays a Toast message accordingly. Of course, if the device is not Wi-Fi enabled (a an emulator is typically not), then the Wi-Fi service is not toggled.<br /><br />The "BT" button launches the "Wireless & network settings" screen. (On some versions of Android, this is the "Wireless controls" screen.)<br /><br /><h3>Problems Observed During Installation And Use</h3>While installing GeekYouUp's Battery Widget, and subsequently using it, the following problems were observed.<br /><br /><b>Permissions Inexplicably Required</b><br /><br />It is surprising to see that a battery level indicator requires as many permissions as Battery Widget does, especially when other battery level indicator App Widgets don't require any permissions. Most suspect amongst the required permissions is "PREVENT DEVICE FROM SLEEPING". (Oh no! Is this battery level indicator going to stop my device from sleeping just to read and display the battery level?) Disappointingly, the application documentation on the Market provides no explanation for the needed permissions. (The answers must be in the code which we get to below.) Whatever the reasons are, they should be explained in the application documentation on the Android market, so users could make informed decisions before installation.<br /><br /><b>Initial Display State Is Incorrect</b><br /><br />New Battery Widget instances incorrectly initially display 0% battery power, though the display does quickly update to show the current battery level.<br /><br /><div style="text-align: center;"><img style="display:block; margin:0px auto 10px; text-align:center; width: 76px; height: 86px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-gvdgL3mSv8Sae5MKHEyplEjjLLZ_OrokqdSy18kkRFDMqHyY0AB-hTox1WBVZ3in56ltDmaXTMvfhmciRMJDFCKhM5y0abTOuzvmE9WSt_JhXqMmiLIz018MiTj8Ja5bWu_AddrVul8/s1600/Battery_Widget_Initial_Display.png" alt="Initial Battery Widget Display" id="BLOGGER_PHOTO_ID_5608909217714015842" border="0" />Initial Battery Widget Display</div><br />A possible fix for this minor issue would be to show a graphic that clearly indicates the App Widget is initializing, instead of initially displaying a 0% battery power graphic. For example, the graphic could be labeled "Loading...", or it could just not have an initial label.<br /><br /><div style="text-align: center;"><img style="display:block; margin:0px auto 10px; text-align:center; width: 76px; height: 86px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoT1M0N-N2TLWYrshnppeARlYROpYpJe77ES8MG17ITUScXPxNpArkrDyVNrM7xLGoKxiwAbSwOvPAh9z-dLRe8KVTQ5hAZ33FIsDRSIf_0ZoEed1nYPjaMPXJr96pC1dVTLBmaThtee0/s1600/Battery_Widget_Initial_Display_Fixed.png" alt="Initial Battery Widget Display Fixed" id="BLOGGER_PHOTO_ID_5608909220652205618" border="0" />Initial Battery Widget Display Fixed</div><br /><b>Force Close On Android 1.5</b><br /><br />Using the latest code from svn, on Android 1.5, clicking on the Battery Widget instance generated a Force Close error. (This problem was not observed on Android 1.6, 2.1 or 2.2. This problem was not observed using the APK from the Android Market.) Nothing in the user interface indicates what the problem is. The answer must be in the code (which we get to below).<br /><br /><b>Non-Functioning "Battery" Button On Android 1.5</b><br /><br />If the device is running a version of Android that does not have the built-in "Battery use" screen, e.g., an Android 1.5 device, then clicking on the "Battery" button appears to do nothing (though it does throw an exception that is not displayed to the user). (The exception message is <code>"ERROR/BatteryWidget(770): android.content.ActivityNotFoundException: No Activity found to handle Intent { action=android.intent.action.POWER_USAGE_SUMMARY }"</code>.)<br /><br />A decent solution to this problem would probably be to remove the "Battery" button from the view on devices that don't support <code>POWER_USAGE_SUMMARY</code> intents.<br /><br /><b>Is It Draining Battery Power?</b><br /><br />Since App Widgets that update more frequently will use more battery power than if they updated less frequently, we are of course interested in how often the Battery Widget polls for or receives changes, and in how often it updates the App Widget display, especially since it would seem reasonable that a battery monitoring application might make frequent updates, so that it can display accurate real-time information. Unfortunately, Battery Widget does not provide for a user-specified update interval. Also, it does not provide any documentation about update frequencies.<br /><br />While users can see in the "Running services" screen (on Android devices that have this feature) that Battery Widget almost constantly has a service running, this does not necessarily mean that it's significantly using battery power. In fact, I can anecdotally report that on both my stock G1 and on my stock G2, I've noticed no difference in battery drain after installing this app. (I usually recharge my stock G1 every two days. During heavier use, I'll recharge after about 36 hours. I usually recharge my stock G2 at most twice per week. So far, my charging frequencies haven't changed after installing GeekYouUp's Battery Widget. It's been 4d 16h 14m since the last charge on the G2, with Battery Widget installed and running the entire time. (I do not currently pop-charge. I wait until the phone dies before connecting any cables. I may be changing this years-long habit based on recommended lithium-ion management practices described at <a target="_blank" href="http://en.wikipedia.org/wiki/Lithium-ion_battery">http://en.wikipedia.org/wiki/Lithium-ion_battery</a>, which is based on information at <a target="_blank" href="http://batteryuniversity.com/learn/article/charging_lithium_ion_batteries">http://batteryuniversity.com/learn/article/charging_lithium_ion_batteries</a>.)) Other users, however, have reported that this app "drained" their battery. But anecdotes and guesswork aren't enough. So, onward to the code...<br /><br /><h3>In The Code</h3><b>Permissions Inexplicably Required</b><br /><br />The Android Manifest of Battery Widget declares that it uses the following permissions: <code>ACCESS_FINE_LOCATION</code>, <code>ACCESS_WIFI_STATE</code>, <code>CHANGE_WIFI_STATE</code>, <code>READ_PHONE_STATE</code>, <code>WAKE_LOCK</code>, and <code>WRITE_SETTINGS</code>.<br /><br />Use of <code>ACCESS_WIFI_STATE</code> and <code>CHANGE_WIFI_STATE</code> is clearly tied to the application feature that toggles Wi-Fi. Perhaps unexpectedly, so is <code>WAKE_LOCK</code>, and the application makes no other use of these permissions. (So, no, Battery Widget is not actually going prevent your device from sleeping.)<br /><br />Use of <code>ACCESS_FINE_LOCATION</code> is similarly explained. The Battery Widget application attempts to access GPS state, to set the color of the GPS button label accordingly.<br /><br />The declaration that <code>READ_PHONE_STATE</code> and <code>WRITE_SETTINGS</code> are used appears to be unnecessary. With these declarations removed, the application still works as expected (with no security permissions errors or other changes observed).<br /><br /><b>Force Close On Android 1.5</b><br /><br />After receiving the Force Close screen when clicking on the App Widget instance running on the Android 1.5 emulator, in LogCat the reported error message was:<br /><pre><code>WARN/dalvikvm(777): threadid=3: thread exiting with uncaught exception (group=0x4000fe70)<br />ERROR/AndroidRuntime(777): Uncaught handler: thread main exiting due to uncaught exception<br />ERROR/AndroidRuntime(777): java.lang.VerifyError: com.geekyouup.android.widgets.battery.TranslucentBlurActivity<br />ERROR/AndroidRuntime(777): at java.lang.Class.newInstanceImpl(Native Method)<br />ERROR/AndroidRuntime(777): at java.lang.Class.newInstance(Class.java:1472)<br />ERROR/AndroidRuntime(777): at android.app.Instrumentation.newActivity(Instrumentation.java:1097)</code></pre>While this didn't point to a line of code or refer to a common error message, it at least mentioned <code>TranslucentBlurActivity</code>, where the problem was easy to find. <code>TranslucentBlurActivity</code> uses an Android API component introduced with level 4. Since Andriod 1.5 uses API level 3, this is a problem. The offending code is the use of <code>android.os.Build.VERSION.SDK_INT</code>.<br /><br />The fix is to change the use of <code>VERSION.SDK_INT</code> back to use <code>VERSION.SDK</code>, as previous versions of the code base did. With this fix in place, Battery Widget deploys and runs fine on Android 1.5 (with the exception of the "Battery" button, of course.)<br /><br /><b>Is It Draining Battery Power?</b><br /><br />GeekYouUp's Battery Widget does not directly update the App Widget display from <code>onUpdate(Context, AppWidgetManager, int[])</code> in its <code>AppWidgetProvider</code>. Instead, like many App Widgets, <code>onUpdate</code> starts an update service to perform updates in a background process. Battery Widget does attempt to configure the <code>AppWidgetProviderInfo</code> to update every five minutes, but the App Widget framework disregards this value, and instead <a target="_blank" href="http://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo.html#updatePeriodMillis">sends update requests every thirty minutes</a>. On my stock G1 and on my stock G2, every call to start the update service does cause the service's <code>onStart(Intent, int)</code> to run, without consideration for whether the service were already running. <code>onStart</code> then updates the display of any Battery Widget App Widget instances.<br /><br />In addition to these scheduled updates, the update service also registers a <code>BroadcastReceiver</code> to respond to all <code>ACTION_BATTERY_CHANGED</code> broadcasts. When this <code>BroadcastReceiver</code> receives such broadcasts, similar to this application's <code>AppWidgetProvider</code>, it starts the update service, which then updates the display of any Battery Widget instances, as previously described.<br /><br />After performing any display updates, instead of stopping itself, as one might expect a well-behaved App Widget update service would do, Battery Widget's update service keeps running, though it does not perform any further processing. It does so in order to keep the <code>ACTION_BATTERY_CHANGED</code> broadcast receiver alive, because <code>ACTION_BATTERY_CHANGED</code> notifications do not wake receivers. (That <code>ACTION_BATTERY_CHANGED</code> receivers only receive notifications when they are running is unexpectedly documented in <a target="_blank" href="http://developer.android.com/reference/android/content/Intent.html#ACTION_POWER_DISCONNECTED"><code>Intent.ACTION_POWER_DISCONNECTED</code></a>, but not mentioned in the <code>ACTION_BATTERY_CHANGED</code> section.) So, to respond to battery changes, an application must either be running, in which case it can immediately respond to <code>ACTION_BATTERY_CHANGED</code> notifications, or it must explicitly check for battery changes according to a schedule, or in response to some other event.<br /><br />This current processing does leave room for some optimizations to reduce updates, without adversely affecting Battery Widget's ability to display up-to-date battery information. Unnecessary processing can be eliminated by not updating the display if the display already correctly reflects the current battery level and charging state. With this change, display updates were reduced by about 40% on my G1 (from an average 5.9 updates per hour, to 3.4), and by a little more than 65% on my G2 (from an average 2.8 updates per hour, to 0.9), during normal use.<br /><br />It's worth noting that my "normal" use significantly differs from many folks' use, as I don't often play videos, music or graphics intensive games. Also, I leave wireless features off when not in use. This undoubtedly means that my devices use power at relatively slower rates, resulting in slower battery charge level change rates, resulting in more significant Battery Widget update reductions than what greater power users would experience.<br /><br /><b>More Aggressive Power Saving Changes</b><br /><br />While it might take a fancy power meter to determine what (if any) affects on power draw these changes make, Battery Widget should be a good App Widget citizen, by providing users the ability to configure how frequently updates occur, and the ability to specify whether the update service should remain running. (This post is already long, so I'll not further rehash common arguments on this topic.)<br /><br />To this end, a simple implementation would be to provide another button on the current "Settings" Activity screen, that when clicked presents users with Battery Widget configuration options for specifying the duration between scheduled updates, and whether to run a background service to automatically respond to <code>ACTION_BATTERY_CHANGED</code> broadcasts. This new options screen would also be a good place to provide users with information about how these settings affect application behavior and possibly affect power consumption.<br /><br />Additional changes that might marginally improve power consumption further include:<br /><ul><li>When the screen is off, don't process any updates, and don't leave any services running. While this seems to be a good idea on the surface, it might not significantly reduce power consumption, as the Android system appears to put dormant processes to sleep when the screen is off and some inactivity threshold is met.</li><li>When the screen is turned on, if the update service was turned off when the screen was turned off, update the widget display, if appropriate, and start the <code>ACTION_BATTERY_CHANGED</code> receiver.</li><li>Provide a quick link to toggle NFC.</li></ul><br /><b>One More Small Change: Consider Device Scale</b><br /><br />The <code>BatteryManager</code> documentation describes that the battery level is not necessarily a value from 0 to 100, but instead is a value "from 0 to <code>EXTRA_SCALE</code>". GeekYouUp's Battery Widget code currently assumes that the max level value is 100. The fix for this is to apply the calculation <code>100 * currentLevel / scale</code> to get the correct level percentage for display on the App Widget.<br /><br /><b>...And Finally The Last Change: Workaround For Android 1.5 Delete App Widget Bug</b><br /><br /><a target="_blank" href="http://groups.google.com/group/android-developers/browse_thread/thread/365d1ed3aac30916?pli=1">As discussed in the Android Developers mailing list</a>, Android 1.5 has a bug where updates for deleted App Widgets continue to process. The workaround "fix" for this bug is to check for <code>ACTION_APPWIDGET_DELETED</code> Intents in <code>onReceive</code> of the <code>AppWidgetProvider</code>, and ensure that <code>onDeleted</code> is called accordingly.<br /><br /><h3>References And Resources:</h3><ul><li><a target="_blank" href="https://market.android.com/details?id=com.geekyouup.android.widgets.battery">GeekYouUp's Battery Widget on the Android Market</a></li><li><a target="_blank" href="http://code.google.com/p/batterywidget/">GeekYouUp's Battery Widget Source Code</a></li><li><a target="_blank" href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=services/java/com/android/server/BatteryService.java">com.android.server.BatteryService.java Source Code</a> -- For information on when <code>ACTION_BATTERY_CHANGED</code> notifications are sent and the data associated with them.</li><li><a target="_blank" href="http://developer.android.com/reference/android/os/BatteryManager.html">BatteryManager Class Documentation</a></li><li><a target="_blank" href="http://developer.android.com/reference/android/content/Intent.html">Intent Class Documentation</a></li></ul><br />FINIProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com3tag:blogger.com,1999:blog-5325426185501267599.post-52478387021340150992011-04-26T14:48:00.064-05:002014-03-19T10:42:17.105-05:00A Simple, Complete App Widget - Part 1<table border="0"><tbody><tr><td><strong>Note:</strong> This blog post is incomplete, and I discontinued posting part 2, as the Widget API and abilities were changing significantly with new releases of Android, at the time of writing. I left this post online, as it has many views, and some useful content.<br /><br />While information on developing <a target="_blank" href="http://developer.android.com/guide/topics/appwidgets/index.html">App Widgets for the Android home screen</a> is plentiful, much of what's available - including the contents of many books on Android software development - is incomplete, often broken, and leads to improperly functioning App Widgets. One all too common outcome is App Widgets that unnecessarily run users' batteries down. Another frequent fail includes funky App Widget behavior if more than one instance exists on a user's home screen. This post is intended to provide a complete blueprint for folks to follow, to develop their own App Widgets that display dynamic content while consuming minimal device resources, provide standard user input controls, and otherwise work correctly as expected.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/RN1ga" alt="QR Code Link To This Article" border="0" />http://goo.gl/RN1ga</div></td></tr></tbody></table><a name='more'></a><br /><h3>Overview Of The App To Be Created</h3><br />Following are two cropped screenshots of the application to be created: one of the app widget, and the other of the activity called when the user clicks the "Save Code" button on the app widget.<br /><br /><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_RlWmieJL9D5qhLra1gbPaFmpEJjyKvNo5ukt00uFQyE7yOZGpUKwdzO7A5rgOGjhKK3H3Xp6XkK4mXvG6RMaig15H0nVNhKYf26eT4378aVSxo6CzgYaVeAr9gmNjolyVcnwj5eP9nw/s800/Random_Passcode_App_Widget_Screenshot.png" /><br /><br /><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJSbgPjr0Y4EbBXx1ezT_L9jmHHWbuOraoxhBgNH9RTA_7_yZDoFZ7hIDHQku1YxF2DsHZQ29xMYwG_-iVaCsYQ8DM-IAJ0B4UNLJ_tHHIyoDfhYRKam4eI1r8GmQ7Ygj9ZQqb1AN9MlA/s800/Save_Random_Passcode_Activity_Screenshot.png" /><br /><br />The App Widget displays a random passcode that changes when the user clicks "New Code" and at the start of the second hour after the last update. The "Save Code" button on the app widget launches the Save Activity, passing it the current passcode.<br /><br /><h3>Code Components</h3><br />(<a target="_blank" href="https://sites.google.com/site/programmerbruce/downloads/A_Simple_Complete_App_Widget-Part_1.zip">Download the complete source code for this example.</a>)<br /><br />The application code consists of the following major parts.<br /><br />1. The Random Passcode App Widget Provider and Service, with configuration and layout xml - Responsible for the App Widget display, updates, event handling, and lifecycle.<br /><br />2. The Random Passcode Save Activity, with layout xml - Receives a passcode from an App Widget, and displays it on screen.<br /><br />3. The Android Manifest - Ties all the application pieces together.<br /><br />(Note: The example code lines are broken at 64 characters for display in Blogger. So, some of the code formatting is funky.)<br /><br /><h3>The App Widget Provider (BroadcastReceiver) and Service</h3><br /><b>RandomPasscodeAppWidgetProvider</b><br /><pre name="code" class="java">/**<br /> * Handles intents for Random Passcode App Widget updates and <br /> * other lifecycle events. Delegates app widget updates to a <br /> * RandomPasscodeAppWidgetService.<br /> */<br />public class RandomPasscodeAppWidgetProvider <br /> extends AppWidgetProvider<br />{<br /> /**<br /> * {@inheritDoc}<br /> */<br /> @Override<br /> public void onUpdate(Context context, AppWidgetManager <br /> appWidgetManager, int[] appWidgetIds)<br /> {<br /> int appWidgetId = INVALID_APPWIDGET_ID;<br /> // Jeff Sharkey's Sky app portends a null check is <br /> // necessary, though the API documentation provides no <br /> // indication that null is possible.<br /> if (appWidgetIds != null)<br /> {<br /> int N = appWidgetIds.length;<br /> if (N == 1)<br /> {<br /> appWidgetId = appWidgetIds[0];<br /> }<br /> }<br /><br /> Intent intent = new Intent(context, <br /> RandomPasscodeAppWidgetService.class);<br /> intent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId);<br /> context.startService(intent);<br /> }<br /> <br /> ...</pre>This calls a Service to handle the initial layout and subsequent updates of App Widgets, instead of directly performing that work in onUpdate. This allows the App Widget Provider to finish quickly, as it should, while any potentially long-running tasks are performed in the background service.<br /><br />Putting the appWidgetId in the Intent extra is a simple way for the RandomPasscodeAppWidgetService to determine whether just one App Widget instance is to be updated, or all of the App Widget instances are to be updated. It's a common mistake of other App Widget example applications to not consider that an update request may be for just one App Widget instance, and instead they simply update all existing App Widgets.<br /><br /><b>RandomPasscodeAppWidgetProvider Continued</b><br /><pre name="code" class="java"> ...<br /><br /> /**<br /> * {@inheritDoc}<br /> */<br /> @Override<br /> public void onReceive(Context context, Intent intent)<br /> {<br /> // Special app widget delete handling added for bug in <br /> // Android 1.5, as described at <br /> // http://groups.google.com/group/android-developers/<br /> // browse_thread/thread/365d1ed3aac30916?pli=1<br /> String sdk = android.os.Build.VERSION.SDK;<br /> String release = android.os.Build.VERSION.RELEASE;<br /> String action = intent.getAction();<br /> if ((sdk.equals("3") || release.equals("1.5")) && <br /> ACTION_APPWIDGET_DELETED.equals(action))<br /> {<br /> int appWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID,<br /> INVALID_APPWIDGET_ID);<br /> if (appWidgetId != INVALID_APPWIDGET_ID)<br /> {<br /> this.onDeleted(context, new int[] { appWidgetId });<br /> }<br /> }<br /> else<br /> {<br /> super.onReceive(context, intent);<br /> }<br /> }<br />}</pre>Be warned, Android has bugs. This code includes a workaround for a failure of Android 1.5 to properly remove deleted App Widget instances. Without this workaround, subsequent App Widget updates will continue to include the deleted App Widget ID in the list of App Widgets to update. Review <a target="_blank" href="http://blog.elsdoerfer.name/2009/06/03/writing-an-android-widget-what-the-docs-dont-tell-you/">the post at blog.elsdoerfer.name on App Widget problems</a> for other things to be wary of.<br /><br /><b>RandomPasscodeAppWidgetService</b><br /><pre name="code" class="java">/**<br /> * A work queue processor that handles asynchronous Random <br /> * Passcode App Widget update requests.<br /> * <br /> * As a "good citizen", using minimal battery to complete <br /> * tasks, this service is started as a needed, and stops <br /> * itself when it runs out of work.<br /> */<br />public class RandomPasscodeAppWidgetService <br /> extends IntentService<br />{<br /> ...<br /> <br /> /**<br /> * {@inheritDoc}<br /> */<br /> @Override<br /> protected void onHandleIntent(Intent intent)<br /> {<br /> AppWidgetManager appWidgetManager = <br /> AppWidgetManager.getInstance(this);<br /><br /> int incomingAppWidgetId = intent.getIntExtra(<br /> EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);<br /> if (incomingAppWidgetId != INVALID_APPWIDGET_ID)<br /> {<br /> updateOneAppWidget(appWidgetManager, <br /> incomingAppWidgetId);<br /> }<br /> else<br /> {<br /> updateAllAppWidgets(appWidgetManager);<br /> }<br /><br /> scheduleNextUpdate();<br /> }<br /><br /> /** <br /> * Schedules the next App Widget update to occur at the <br /> * start of the fourth hour after the current time. Any <br /> * previously scheduled App Widget update is effectively <br /> * canceled and replaced by the newly scheduled update.<br /> * <br /> * The scheduled update does not wake the device up. If <br /> * the update is scheduled to start while the device is <br /> * asleep, it will not run until the next time the device <br /> * is awake.<br /> */<br /> private void scheduleNextUpdate()<br /> {<br /> Intent changePasscodeIntent = <br /> new Intent(this, this.getClass());<br /> // A content URI for this Intent may be unnecessary.<br /> changePasscodeIntent.setData(Uri.parse("content://" + <br /> PACKAGE_NAME + "/change_passcode"));<br /> PendingIntent changePasscodePendingIntent =<br /> PendingIntent.getService(this, 0, changePasscodeIntent, <br /> PendingIntent.FLAG_UPDATE_CURRENT);<br /><br /> // The update frequency should be user configurable.<br /> Time time = new Time();<br /> time.set(System.currentTimeMillis() + 4 * <br /> DateUtils.HOUR_IN_MILLIS);<br /> time.minute = 0;<br /> time.second = 0;<br /> long nextUpdate = time.toMillis(false);<br /><br /> AlarmManager alarmManager = <br /> (AlarmManager) getSystemService(Context.ALARM_SERVICE);<br /> alarmManager.set(AlarmManager.RTC, nextUpdate, <br /> changePasscodePendingIntent);<br /> }<br /><br /> ...</pre><br /><pre name="code" class="java"> ...<br /><br /> /** <br /> * For each random passcode app widget on the user's home <br /> * screen, updates its display with a new passcode, and <br /> * registers click handling for its buttons.<br /> */<br /> private void updateAllAppWidgets(AppWidgetManager <br /> appWidgetManager)<br /> {<br /> ComponentName appWidgetProvider = new ComponentName(this, <br /> RandomPasscodeAppWidgetProvider.class);<br /> int[] appWidgetIds = <br /> appWidgetManager.getAppWidgetIds(appWidgetProvider);<br /> int N = appWidgetIds.length;<br /> for (int i = 0; i < N; i++)<br /> {<br /> int appWidgetId = appWidgetIds[i];<br /> updateOneAppWidget(appWidgetManager, appWidgetId);<br /> }<br /> }<br /><br /> /** <br /> * For the random passcode app widget with the provided ID, <br /> * updates its display with a new passcode, and registers <br /> * click handling for its buttons.<br /> */<br /> private void updateOneAppWidget(AppWidgetManager <br /> appWidgetManager, int appWidgetId)<br /> {<br /> String newRandomPasscode = generateRandomPasscode();<br /> RemoteViews views = new RemoteViews(PACKAGE_NAME, <br /> R.layout.app_widget_layout);<br /> views.setTextViewText(R.id.passcode_view, <br /> newRandomPasscode);<br /><br /> setSavePasscodeIntent(views, appWidgetId, <br /> newRandomPasscode);<br /> setChangePasscodeIntent(views, appWidgetId);<br /><br /> appWidgetManager.updateAppWidget(appWidgetId, views);<br /> }<br /><br /> ...</pre><br /><pre name="code" class="java"> ...<br /><br /> /** <br /> * Configures "Save Code" button clicks to pass the current <br /> * passcode of the parent app widget to the save passcode <br /> * Activity.<br /> */<br /> private void setSavePasscodeIntent(RemoteViews views, <br /> int appWidgetId, String newRandomPasscode)<br /> {<br /> Intent savePasscodeIntent = <br /> new Intent(this, SaveRandomPasscodeActivity.class);<br /> savePasscodeIntent.setData(Uri.parse("content://" + <br /> PACKAGE_NAME + "/save_passcode/widget_id/" + <br /> appWidgetId));<br /> savePasscodeIntent.putExtra("PASSCODE", newRandomPasscode);<br /> PendingIntent savePasscodePendingIntent =<br /> PendingIntent.getActivity(this, 0, savePasscodeIntent, <br /> PendingIntent.FLAG_UPDATE_CURRENT);<br /> views.setOnClickPendingIntent(R.id.save_passcode_button, <br /> savePasscodePendingIntent);<br /> }<br /><br /> /** <br /> * Configures "New Code" button clicks to generate and set a <br /> * new passcode on the parent app widget.<br /> */<br /> private void setChangePasscodeIntent(RemoteViews views, <br /> int appWidgetId)<br /> {<br /> Intent changePasscodeIntent = <br /> new Intent(this, this.getClass());<br /> changePasscodeIntent.setData(Uri.parse("content://" + <br /> PACKAGE_NAME + "/change_passcode/widget_id/" + <br /> appWidgetId));<br /> changePasscodeIntent.putExtra(EXTRA_APPWIDGET_ID, <br /> appWidgetId);<br /> PendingIntent changePasscodePendingIntent =<br /> PendingIntent.getService(this, 0, changePasscodeIntent, <br /> PendingIntent.FLAG_UPDATE_CURRENT);<br /> views.setOnClickPendingIntent(R.id.new_passcode_button, <br /> changePasscodePendingIntent);<br /> }<br /> <br /> ...</pre>Note the calls to Intent.setData(Uri) with a content URI String unique for each App Widget instance. Explanations for exactly what happens if this value were not set on the Intent differ, and I haven't yet dug through the source code to see which one is correct. At any rate, when one of the buttons on the App Widget were clicked, without these unique Uris, the same Intent would be passed to the Activity or Service, without proper consideration for which App Widget originated the action. To clarify, it would be possible that when a button from App Widget instance "B" were clicked, a value from App Widget instance "A" would be included in the Intent passed to the Activity or Process. If this problem still isn't clear, just remove the lines of code that set the data Uris, create multiple instances of the App Widget on the home screen, click on them, and see what happens.<br /><br /><b>RandomPasscodeAppWidgetService Continued</b><br /><pre name="code" class="java"> ...<br /><br /> /** <br /> * Generates a string, eight characters in length, comprised <br /> * of ASCII characters randomly selected from the range of <br /> * character 33, '!', to character 126, '~'.<br /> * <br /> * Security Note: The random passcode generator of this <br /> * service is a toy implementation that uses java.util.Random<br /> * for random values. A random passcode generator for real <br /> * world use should use a cryptographically secure <br /> * pseudorandom number generator, or a hardware random number<br /> * generator, both of which java.util.Random does not do. For<br /> * more information, see <br /> * http://en.wikipedia.org/wiki/<br /> * Cryptographically_secure_pseudorandom_number_generator<br /> */<br /> private static String generateRandomPasscode()<br /> {<br /> Random random = new Random();<br /> int targetLength = 8;<br /> char[] passcode = new char[targetLength];<br /> for (int i = 0; i < targetLength; i++)<br /> {<br /> passcode[i] = (char) (random.nextInt(94) + 33);<br /> }<br /> return new String(passcode);<br /> }<br />}<br /></pre><br /><br /><b>layout/app_widget_layout.xml</b><br /><pre name="code" class="xml"><?xml version="1.0" encoding="utf-8"?><br /><LinearLayout<br /> xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="horizontal"<br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content"<br /> android:padding="10dip"<br /> android:gravity="center"<br /> android:background="#88888888"<br /> android:weightSum="3"><br /> <TextView<br /> android:id="@+id/passcode_view"<br /> android:layout_width="0dip"<br /> android:layout_height="wrap_content"<br /> android:layout_weight="1"<br /> android:text="Loading..."<br /> android:gravity="center"<br /> android:background="@android:color/black"<br /> android:textColor="@android:color/white" /><br /> <Button<br /> android:id="@+id/new_passcode_button"<br /> android:layout_width="0dip"<br /> android:layout_height="wrap_content"<br /> android:layout_weight="1"<br /> android:text="New Code" /><br /> <Button<br /> android:id="@+id/save_passcode_button"<br /> android:layout_width="0dip"<br /> android:layout_height="wrap_content"<br /> android:layout_weight="1"<br /> android:text="Save Code" /><br /></LinearLayout></pre><br /><br /><b>xml/initial_app_widget_provider_config.xml</b><br /><pre name="code" class="xml"><?xml version="1.0" encoding="utf-8"?><br /><appwidget-provider<br /> xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:minWidth="290dip"<br /> android:minHeight="72dip"<br /> android:updatePeriodMillis="0"<br /> android:initialLayout="@layout/app_widget_layout" /></pre><br /><br /><br /><h3>The Random Passcode Save Activity</h3><pre name="code" class="java">/**<br /> * Receives passcode save requests from Random Passcode App <br /> * Widgets, and displays the passcode to the user.<br /> * <br /> * Note: This example activity doesn't actually save anything. <br /> * It just displays an incoming passcode.<br /> */<br />public class SaveRandomPasscodeActivity extends Activity<br />{<br /> /**<br /> * {@inheritDoc}<br /> */<br /> @Override<br /> public void onCreate(Bundle savedInstanceState)<br /> {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.main);<br /><br /> Intent intent = getIntent();<br /> String passcode = intent.getStringExtra("PASSCODE");<br /> String message =<br /> passcode == null ?<br /> "Select a passcode to save from an app widget " + <br /> "on the home screen."<br /> : "Passcode " + passcode + " saved.";<br /> TextView tv = <br /> (TextView) findViewById(R.id.passcode_saved_view);<br /> tv.setText(message);<br /> }<br />}</pre><br /><br /><b>layout/main.mxl</b><br /><pre name="code" class="xml"><?xml version="1.0" encoding="utf-8"?><br /><LinearLayout<br /> xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"><br /> <TextView<br /> android:id="@+id/passcode_saved_view"<br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content"<br /> android:text="Loading..."<br /> android:textSize="24dip" /><br /></LinearLayout></pre><br /><br /><br /><h3>The Android Manifest</h3><pre name="code" class="xml"><?xml version="1.0" encoding="utf-8"?><br /><manifest<br /> xmlns:android="http://schemas.android.com/apk/res/android"<br /> package=<br /> "com.androidprogrammingexamples.simplecompleteappwidget"<br /> android:versionCode="1"<br /> android:versionName="1.0"><br /> <uses-sdk<br /> android:minSdkVersion="3" /><br /><br /> <application<br /> android:icon="@drawable/icon"<br /> android:label="@string/app_name"><br /> <activity<br /> android:name=".SaveRandomPasscodeActivity"<br /> android:label="@string/app_name"><br /> <intent-filter><br /> <action<br />android:name="android.intent.action.MAIN" /><br /> <category<br />android:name="android.intent.category.LAUNCHER" /><br /> </intent-filter><br /> </activity><br /> <receiver<br /> android:name=".RandomPasscodeAppWidgetProvider"<br /> android:label="Random Passcode"><br /> <intent-filter><br /> <action<br />android:name="android.appwidget.action.APPWIDGET_UPDATE" /><br /> </intent-filter><br /> <meta-data<br /> android:name="android.appwidget.provider"<br />android:resource="@xml/initial_app_widget_provider_config" /><br /> </receiver><br /> <service<br /> android:name=".RandomPasscodeAppWidgetService" /><br /> </application><br /></manifest></pre>The configurations in the Android Manifest are typical for an Activity and an App Widget. Note, the service of the application is also registered here.<br /><br /><h3>Other Implementation Notes</h3><br />In this implementation, button clicks launch PendingIntents that directly start an Activity or directly start a Service. A different approach many Android applications take is to instead configure PendingIntents that broadcast an Intent, for any interested and properly configured BroadcastReceivers to receive and handle. This broadcasting approach provides decoupling between the broadcaster and receiver, since the broadcaster doesn't need any knowledge of the receiver, allowing for a "plug-in architecture", where other receivers can register to handle the broadcasts. For example, someone could write a receiver to respond to every passcode save broadcast, to email the passcode to our GMail account. (This is not a suggestion for a good idea - it's just an example.)<br /><br />The example implementation of this blog post was designed to demonstrate that it's not necessary to use the broadcasting approach with App Widget event handling (though the App Widget "Provider" is a BroadcastReceiver responding to APPWIDGET_UPDATE broadcasts).<br /><br />For a decent example of an App Widget with event broadcasting and receiving, see <a target="_blank" href="http://android.git.kernel.org/?p=platform/packages/apps/Protips.git">the "Home screen tips" widget</a>, which is part of stock Android builds. (Note, the "Home screen tips" widget does not handle events per specific App Widget instance, instead applying events and display state to all instances the same, such that no two instances will display different messages at the same time.)<br /><br /><h3>References And How I Arrived At This Implementation</h3><br />The first time I wanted to create an App Widget, I figured I'd be able to just follow a few of the many examples available on the Internet, in the official Android developer guide, in the various Android programming books, and in the Android platform code base. That didn't work out very well. I think every example I tested included at least one bug (like failing to accommodate multiple instances, and leaving services running, though they weren't in use), and/or otherwise didn't provide for the user interactions I wanted. Luckily, the Android platform is open source, and one is able to see exactly what happens behind the scenes.<br /><br />Recognizing that much of the App Widget information in the following resources is incomplete for high quality, real world applications, they still provide alternative examples worth reviewing.<br /><br /><b>Books</b><ul><li><a target="_blank" href="http://www.amazon.com/Android-Action-Frank-Ableson/dp/1935182722/">Android in Action by Frank Ableson</a> - Includes one of the best, most complete App Widget examples.</li><li><a target="_blank" href="http://www.amazon.com/Professional-Android-Application-Development-Programmer/dp/0470565527/">Professional Android 2 Application Development by Reto Meier</a></li><li><a target="_blank" href="http://www.amazon.com/Beginning-Android-2-Mark-Murphy/dp/1430226293/">Beginning Android 2 by Mark Murphy</a></li></ul><br /><br /><b>Other Examples</b><ul><li>The "Home screen tips" widget - <a target="_blank" href="http://android.git.kernel.org/?p=platform/packages/apps/Protips.git">http://android.git.kernel.org/?p=platform/packages/apps/Protips.git</a></li><li>Jeff Sharkey's post introducing the App Widget framework - <a target="_blank" href="http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html">http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html</a></li><li>Jeff Sharkey's Forcast Widget - <a target="_blank" href="http://code.google.com/p/android-sky">http://code.google.com/p/android-sky</a></li><li>The CommonsWare Books (has links to source code examples) - <a target="_blank" href="http://commonsware.com/Android/">http://commonsware.com/Android/</a></li><li>The official API Demos App, preinstalled on Android emulators - <a target="_blank" href="http://developer.android.com/resources/samples/ApiDemos/">http://developer.android.com/resources/samples/ApiDemos/</a></li><li>The Android Platform Calendar App Widget - <a target="_blank" href="http://android.git.kernel.org/?p=platform/packages/providers/CalendarProvider.git;a=blob;f=src/com/android/providers/calendar/CalendarAppWidgetProvider.java;h=928d4bc6027a48e9c31de882b9f214efb2cb0207;hb=cupcake">http://android.git.kernel.org/?p=platform/packages/providers/CalendarProvider.git;a=blob;f=src/com/android/providers/calendar/CalendarAppWidgetProvider.java;h=928d4bc6027a48e9c31de882b9f214efb2cb0207;hb=cupcake</a></li></ul><br /><br /><b>Additional Resources</b><ul><li>The App Widgets Dev Guide - <a target="_blank" href="http://developer.android.com/guide/topics/appwidgets/index.html">http://developer.android.com/guide/topics/appwidgets/</a></li><li>App Widget Design Guidelines - <a target="_blank" href="http://developer.android.com/guide/practices/ui_guidelines/widget_design.html">http://developer.android.com/guide/practices/ui_guidelines/widget_design.html</a></li><li>AppWidgetProvider API Documentation - <a target="_blank" href="http://developer.android.com/reference/android/appwidget/AppWidgetProvider.html">http://developer.android.com/reference/android/appwidget/AppWidgetProvider.html</a></li></ul><br />FINIProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com2tag:blogger.com,1999:blog-5325426185501267599.post-68766036443915677752011-04-10T08:47:00.018-05:002011-05-26T16:55:38.221-05:00Android Widget Design Guidelines - Widget SizesWhile <a target="_blank" href="http://developer.android.com/guide/practices/ui_guidelines/widget_design.html">the Android Widget Design Guidelines</a> do clearly list the bounding box dimensions for widgets, they don't list recommended dimensions for the outer frame or the inner edge box. The following apparent recommendations were extracted from the standard widget frames Photo$hop files they provided for download.<br /><br /><table border="1"><tr><th colspan="4">Portrait Mode</th></tr><tr><th></th><th>4 x 1</th><th>3 x 3</th><th>2 x 2</th></tr><tr><td>Bounding Box:</td><td>320 x 100</td><td>240 x 300</td><td>160 x 200</td></tr><tr><td>Outer Frame with Shadow:</td><td>304 x 75</td><td>224 x 272</td><td>144 x 189</td></tr><tr><td>Inner Edge Box:</td><td>287 x 58</td><td>207 x 256</td><td>129 x 174</td></tr></table><br /><table border="1"><tr><th colspan="4">Landscape Mode</th></tr><tr><th></th><th>4 x 1</th><th>3 x 3</th><th>2 x 2</th></tr><tr><td>Bounding Box:</td><td>424 x 74</td><td>318 x 222</td><td>212 x 148</td></tr><tr><td>Outer Frame with Shadow:</td><td>412 x 63</td><td>305 x 200</td><td>199 x 136</td></tr><tr><td>Inner Edge Box:</td><td>395 x 46</td><td>288 x 182</td><td>184 x 121</td></tr></table><br /><br />Unfortunately, these dimensions don't all nest with even spacings. For example, the portrait-mode inner edge box has a width of 207, which leaves 33 pixels of padding to split between the left and right sides, but 33 is an odd number, and we cannot split a pixel in half, so one is left to decide what to do with an extra pixel. My solution is to simply shrink the inner edge box width to 206 pixels.<br /><br /><table border="0"><tbody><tr><td>Of course, different screens will actually use different numbers of pixels both horizontally and vertically (depending on actual screen sizes and pixel densities). For this reason, and to have smaller graphics file sizes, as the guidelines suggest, use 9-patch graphics, but note that scaled graphics might be fuzzy in appearance, and it may be necessary to create LDPI, MDPI and HDPI versions of widget graphics.<br /><br />For <a target="_blank" href="http://getpaint.net">Paint.NET</a>, I made simple templates for the six standard widget sizes. <a href="https://sites.google.com/site/programmerbruce/downloads/TEMPLATE-PACK-widgets.zip">Download</a> and enjoy. (I'm using the term "template" loosely. These templates simply have three layers each with outlined boxes matching the dimensions described above.)</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/C13Nx" alt="QR Code Link To This Article" border="0" />http://goo.gl/C13Nx</div></td></tr></tbody></table>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0tag:blogger.com,1999:blog-5325426185501267599.post-69052256866220018622011-03-24T23:09:00.004-05:002011-06-17T17:37:17.895-05:00Corrections for Android Tutorial "Hello, Views > Spinner"<table border="0"><tbody><tr><td>The Android Developer's guide (available at <a target="_blank" href="http://developer.android.com/guide/index.html">http://developer.android.com/guide/index.html</a>) is an excellent resource for folks looking to develop Android applications. Of course, as is much technical documentation, it's infected with code bugs and errors.<br /><br />In particular, this post lists errors in the "Hello, Views > Spinner" tutorial, available at <a target="_blank" href="http://developer.android.com/resources/tutorials/views/hello-spinner.html">http://developer.android.com/resources/tutorials/views/hello-spinner.html</a>, and includes step-by-step corrections for these problems. This information was communicated to the Android development team in <a target="_blank" href="http://code.google.com/p/android/issues/detail?id=12817">Android Issue 12817</a>.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/P408h" alt="QR Code Link To This Article" border="0" />http://goo.gl/P408h</div></td></tr></tbody></table><a name='more'></a><br /><span style="font-weight:bold;">Step 1</span>: Start a new project named HelloSpinner.<br /><br />The tutorial author(s)/editor(s) may be assuming the folks following this tutorial are already skilled enough to fill in the missing project creation details. For anyone stuck at this first step, take a look at the initial <a target="_blank" href="http://developer.android.com/resources/tutorials/hello-world.html">"Hello, World" tutorial</a>. Following are reasonable values to use for the other fields when creating this new project.<br /><br /><table border="1"><tbody><tr><td align="right">Build Target:</td><td align="left">Android 1.5</td></tr><tr><td align="right">Application name:</td><td>Hello Spinner</td></tr><tr><td align="right">Package name:</td><td>fubar.hellospinner</td></tr><tr><td align="right">Activity:</td><td>HelloSpinner</td></tr><tr><td align="right">Min SDK Version:</td><td>3</td></tr></tbody></table><br />Concerning <span style="font-weight: bold;">step 2</span>, it is (or will be) error free, after the third step is completed. It makes sense to me that steps two and three would be switched, so as to avoid the initial errors reported by Eclipse after completing the second step.<br /><br /><span style="font-weight: bold;">Step 3</span> introduces a significant error, because it instructs users to delete the <code>app_name</code> string resource value, which is actually used by the application. After following step 3 as instructed, save the changes, and notice that Eclipse complains about errors in <code>AndroidManifest.xml</code> concerning missing resource <code>@string/app_name</code>.<br /><br />To correct this problem, instead of editing <code>strings.xml</code> to look like the given example, just add the new resource entries to the existing resource entries, so as not to delete the <code>@string/app_name</code> entry. In other words, following are reasonable contents for the <code>strings.xml</code> file.<br /><pre><code><!--?xml version="1.0" encoding="utf-8"?--><br /><resources><br /> <string name="hello">Hello World, HelloSpinner!</string><br /> <string name="app_name">Hello Spinner</string><br /> <string name="planet_prompt">Choose a planet</string><br /> <string-array name="planets_array"><br /> <item>Mercury</item><br /> <item>Venus</item><br /> <item>Earth</item><br /> <item>Mars</item><br /> <item>Jupiter</item><br /> <item>Saturn</item><br /> <item>Uranus</item><br /> <item>Neptune</item><br /> </string-array><br /></resources></code></pre>Note the entry for "hello" was generated automatically when the project was created. It is not actually used by the application, and can be safely excluded.<br /><br />For <span style="font-weight:bold;">steps 4, 5, and 6</span>, note the author(s)/editor(s) no doubt assumed readers would be able to provide the missing import statements, necessary for the code to compile. For these code changes, the following six items should be imported.<pre><code>import android.view.View;<br />import android.widget.AdapterView;<br />import android.widget.AdapterView.OnItemSelectedListener;<br />import android.widget.ArrayAdapter;<br />import android.widget.Spinner;<br />import android.widget.Toast;</code></pre><span style="font-weight:bold;">Step 5</span> contains a code error that needs to be corrected: it includes an extra closing parenthesis in the middle of the code. Correct this code <pre><code>Toast.makeText(parent.getContext()<span style="color: rgb(255, 0, 0); font-weight: bold;">)</span>, "The planet is " +</code></pre> removing the extra ")" before the comma to then be <pre><code>Toast.makeText(parent.getContext(), "The planet is " +</code></pre>After implementing these corrections, users should be able to complete <span style="font-weight:bold;">step 7</span>, and successfully run the application.<br /><br />Please don't hesitate to post any questions or problems you may still be having with the "Hello, Views > Spinner" tutorial below.<br /><br />Happy Coding!ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com5tag:blogger.com,1999:blog-5325426185501267599.post-51588270281298385642011-03-22T18:53:00.003-05:002011-05-26T16:48:46.493-05:00Android Protector OWNED Easy as 1, 2, 3!<table border="0"><tbody><tr><td>Not content with the original password protection of Android 2.2 on my T-Mobile G2, I went searching through the Android Market for other solutions. Amongst the adware and other junkware I stumbled across <a target="_blank" href="https://market.android.com/details?id=com.androidpassword.core">Android Protector v3.3.1 by Alexander Kosenkov</a>, which looked promising. So, I decided to give it a try, installed it, and set about seeing if it did what it claimed to do, and whether it was easy to defeat.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/u0NZK" alt="QR Code Link To This Article" border="0" />http://goo.gl/u0NZK</div></td></tr></tbody></table><br />The basis of the revolutionary and disruptive approach I took to break Protector stems from playing a lot of video games growing up. If there's one thing I learned during all those precious hours of youth spent in front of the TV with a game controller in hand, it was to TRY AGAIN. When Mario died because I didn't jump far enough, I just tried again. When Mike Tyson KO'd me before I got him, I just tried again. When those re-attempts failed, I just tried again.<br /><br />Applying this life lesson to TRY AGAIN to attempting to defeat Android Protector proved useful. I was able to bypass Android Protector's security measures less than three minutes after installing and using it for the first time.<br /><br />The steps to do so are simple:<br /><ol><li>After installing and configuring Protector, restart the Android phone so the application is enabled, then search for the application in the Android Market, and select it in the search results listing. At this point, Protector covers the screen with a security code input box, otherwise preventing access to the Android Market screen beneath it. Just use the back arrow to return to the home screen.</li><li>Launch the Android Market application, again. The Market app should try to immediately return you to viewing the Protector application listing, but of course, Protector should again cover the screen with the security code input box. After it does, just press the back arrow to return to the home screen.</li><li>Launch the Android Market application, again. Amazingly, the Market app displays the Protector application listing and Protector does <span style="font-style: italic;">not</span> cover the screen with the security code input box. Press the link to Uninstall Protector. Protector is thus defeated without ever needing to input the security code.</li></ol>Android Protector OWNED!<br /><br /><a target="_blank" href="http://www.youtube.com/watch?v=6Kqxga4-xNQ">A short video of these steps in action is available on YouTube.</a><br /><br /><iframe title="YouTube video player" width="640" height="390" src="http://www.youtube.com/embed/6Kqxga4-xNQ" frameborder="0" allowfullscreen></iframe><br /><br />What have you OWNED today?ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0tag:blogger.com,1999:blog-5325426185501267599.post-3658059018438045442011-03-22T02:57:00.003-05:002011-05-26T16:46:46.177-05:00HOWTO: Install T-Mobile G2 USB Driver<table border="0"><tbody><tr><td>While <a target="_blank" href="http://lmgtfy.com/?q=G2+USB+driver">Googling for G2 USB driver</a> does currently lead to <a target="_blank" href="http://forums.t-mobile.com/t5/HTC/HOW-TO-Get-ADB-to-recognize-your-G2/td-p/486751">some</a> <a target="_blank" href="http://forums.t-mobile.com/t5/T-Mobile-G2/USB-Driver/m-p/494162">pages</a> that list working instructions for installing the driver on M$ Windows machines to connect to a T-Mobile G2 Android phone through USB with <a target="_blank" href="http://developer.android.com/guide/developing/tools/adb.html">adb</a>, many of the search results were not useful, and this post is small contribution to help direct folks to a solution. (Plus, who knows what will happen to the info pages at tmobile.com, with <a target="_blank" href="http://newsroom.t-mobile.com/articles/att-acquires-tmobile-USA">AT&T acquiring T-Mobile</a>.)</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/leNAj" alt="QR Code Link To This Article" border="0" />http://goo.gl/leNAj</div></td></tr></tbody></table><br />After installing <a target="_blank" href="http://developer.android.com/index.html">the Android SDK</a>, and selecting to install the latest Google USB Driver package with the SDK and AVD Manager, edit the android_winusb.inf file, located in the directory where the Google USB drivers installed (which currently is android-sdk/extras/google/usb_driver), and paste in the following three lines, at the end of the sections labeled "[Google.NTx86]" and "[Google.NTamd64]". <pre><code>;T-Mobile G2<br />%SingleAdbInterface% = USB_Install, USB\VID_0BB4&PID_0C91<br />%CompositeAdbInterface% = USB_Install, USB\VID_0BB4&PID_0C91&MI_01</code></pre> Then, connect the G2 to the computer with a USB cable, ensure the phone is in debug mode, and follow the remaining instructions at <a target="_blank" href="http://developer.android.com/sdk/win-usb.html">http://developer.android.com/sdk/win-usb.html</a><br /><br />Note that it's sometimes necessary to repeat this process after downloading and updating the Google USB Driver package, as the updates can clear out the previous custom configuration changes.ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com3tag:blogger.com,1999:blog-5325426185501267599.post-48772511903275858982011-03-12T02:47:00.002-06:002011-05-26T16:44:26.523-05:00HOWTO: Clone VirtualBox VM (from existing Hard Disk) and Change the UUID<table border="0"><tbody><tr><td>Currently, Google searching for information on how to <a target="_blank" href="http://lmgtfy.com/?q=clone+virtualbox+vm">clone VirtualBox Virtual Machines</a> or how to <a target="_blank" href="http://lmgtfy.com/?q=move+virtualbox+vm">move a VirtualBox VM</a> (to a new host directory, host disk, or host machine) leads to a lot of folks stuck with cloning failures, apparent dead ends, outdated information, and VirtualBox features that don't seem to work correctly. While poking around enough leads to working solutions, this blog post is a small contribution to help direct folks towards success.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/BOiD6" alt="QR Code Link To This Article" border="0" />http://goo.gl/BOiD6</div></td></tr></tbody></table><br />Note: These steps work for me running VirtualBox 4.0.4 r70112 (and with the latest 4.0.6 r71416), on Windows 7 Home Premium x64 SR1. I am new to using <a target="_blank" href="http://www.virtualbox.org/">VirtualBox</a> and my experience level is yet introductory. Also, the information provided here is listed elsewhere on the internet, and is probably somewhere in the VirtualBox documentation.<br /><br />Without further ado, to copy a flat (i.e., without snapshots) virtual machine, with the VM not running (i.e., stopped), follow these three steps:<br /><ol><li>First, create a copy of the VDI file, using Windows Explorer or a simple copy command: <code>copy abc.vdi def.vdi</code></li><li>Then, change the UUID of the new VDI file with the command <code>VBoxManage.exe internalcommands sethduuid def.vdi</code></li><li>And finally, from within the VirtualBox Manager, create a new Virtual Machine, selecting to use the new VDI as an existing Hard Disk.</li></ol><br />To simply move the location of an existing VDI file on the host, or to copy or move the VDI file to another host, it does work to delete the VM within the VirtualBox Manager, without actually deleting the VDI file on the host file system (obviously, deleting the VM on the current host is not necessary if moving to a new host), move the VDI file to the new location, and then create a new VM, selecting to use the moved VDI file as an existing Hard Disk.<br /><br />Alternatively, to move the VDI file on the host without deleting the existing VM, close the VirtualBox Manager and shut down all VM instances. (Shutting down the VMs not associated to the move might not be necessary.) Then, edit *all* of the file paths in the .vbox file associated with the VM to be moved. By default, the .vbox file is in a subdirectory in the host user's home directory (e.g., C:\Users\User1). The .vbox file name should match the name of the VDI file, and be in a subdirectory with a name that also matches the name of the VDI file. The .vbox file is a simple text XML file, easily edited in Windows Notepad. Finally, start the VirtualBox Manager, select the newly moved VM, which should still be listed on the left side, where all of the VMs are listed, and start it.<br /><br />Note that changing the UUID of the VDI file is not necessary, when simply moving it.ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0tag:blogger.com,1999:blog-5325426185501267599.post-19180648436224349842011-02-22T07:00:00.003-06:002011-05-26T16:40:22.827-05:00FAIL: New Computer First Use: Stack Overflow<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhENq1b3XJqxJoC8pDA8eXyG9-ZDKAjXeC5oqlXNbKbI3hSdQ3k3OqLvFAfjIXRRdojkqxiRlcubVMR5Peyridyh7gDHGFOD5OhBVSkmbb5DNcyxp0YlyLSchfQhMfT6lRJnCs85NdH4iI/s1600/FAIL_-_New_Computer_First_Use.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhENq1b3XJqxJoC8pDA8eXyG9-ZDKAjXeC5oqlXNbKbI3hSdQ3k3OqLvFAfjIXRRdojkqxiRlcubVMR5Peyridyh7gDHGFOD5OhBVSkmbb5DNcyxp0YlyLSchfQhMfT6lRJnCs85NdH4iI/s400/FAIL_-_New_Computer_First_Use.png" alt="" id="BLOGGER_PHOTO_ID_5605155691993238690" border="0" /></a><br />I needed a built computer quickly, so I bought a HP, because it was priced well at a local (national chain) store. And this is what I got when using it for the first time.<br /><br /><table border="0"><tbody><tr><td>I then uninstalled most of the pre-installed apps (including zinio, adobe air, adobe flash, bing bar, blio, hp mediasmart music, hp mediasmart dvd, dvd menu pack for hp mediasmart video, hp mediasmart photo, mp mediasmart smartmenu, hp mediasmart video, hp mediasmart/touchsmart netflix, hp moviestore, hp support assistant, kobo, movie theme pack for hp mediasmart video, norton internet security, norton online backup, pdf complete special edition, pressreader, roxionow player, picturemover, photonow, power2go, powerdirector, hp setup manager, hp games, labelprint, cyberlink dvd suite deluxe, microsoft office 2010, Microsoft SQL Server 2005 Compact Edition [ENU], PlayReady (MS DRM), and Windows Live Essentials), applied security patches, installed apps that I like, and I'm a happy HP user, so far.</td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/OrSQP" alt="QR Code Link To This Article" border="0" />http://goo.gl/OrSQP</div></td></tr></tbody></table>ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0tag:blogger.com,1999:blog-5325426185501267599.post-46731445990391466762010-11-12T22:05:00.008-06:002011-05-26T16:42:14.409-05:00HOWTO: Publish Javadoc on Google Code<table border="0"><tbody><tr><td>Adding to <a target="_blank" href="http://stuffthathappens.com/blog/2007/11/09/howto-publish-javadoc-on-google-code/">Eric Burke's identically titled blog entry from three years ago</a>, this entry details how to ensure Javadoc files have the correct svn:mime-type for normal browser viewing, when files are added to Subversion using the command line svn in M$ Windows.<br /><br />For an example of viewing properly published Javadoc in a Google Code project, take a look at <a target="_blank" href="http://jgenere.googlecode.com/svn/tags/initial_port_v0.3r2010.1105/jgenere-initial_port/javadoc/index.html">http://jgenere.googlecode.com/svn/tags/initial_port_v0.3r2010.1105/jgenere-initial_port/javadoc/index.html</a></td><td><div style="text-align: center;">Link To This Article<img style="display:block; margin:0px auto 0px; text-align:center;width: 150px; height: 150px;" src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&choe=UTF-8&chld=H&chl=http://goo.gl/vM4jz" alt="QR Code Link To This Article" border="0" />http://goo.gl/vM4jz</div></td></tr></tbody></table><br />As described in <a target="_blank" href="http://svnbook.red-bean.com/en/1.5/svn.advanced.props.html">the "Properties" section</a> of <a target="_blank" href="http://svnbook.red-bean.com/">Version Control with Subversion</a>, Subversion includes subcommands to edit file properties, such as mime-type, with the command line tools. Also, <a target="_blank" href="http://svnbook.red-bean.com/en/1.5/svn.advanced.confarea.html">the "Runtime Configuration Area" section</a>, describes setting properties in the Windows registry, or in a "config" file. So, where is this mysterious config file?<br /><br />The Secret Detail: The config file is in the %appdata%/Subversion directory and it's named simply "config", with no filename extension - at least it is on my computer. Notes: 1. On my system, %appdata% is the /Users/USERNAME/AppData/Roaming directory. 2. The %appdata%/Subversion directory wasn't created on my system after just installing svn tooling. (I'm using <a target="_blank" href="http://www.sliksvn.com">Slik SVN</a>.) Instead, the directory was created after the first time I successfully executed a <code>svn checkout</code> command.<br /><br />In the file named simply "config", just before the [auto-props] section, uncomment (by deleting any # characters in front of it) or add an entry for "enable-auto-props = yes", and then add the following three configurations to the [auto-props] section.<br /><ul><li>*.html = svn:mime-type=text/html</li><li>*.css = svn:mime-type=text/css</li><li>*.gif = svn:mime-type=image/gif</li></ul>Of course, if the content to be published contains files with other extensions, add auto-props <a target="_blank" href="http://www.w3schools.com/media/media_mimeref.asp">mime-type</a> configurations for each of them, as well.<br /><br />After editing and saving the config file, simply execute <code>svn add</code> and <code>svn commit</code> commands to add the Javadoc to the repository. If the docs were already in the repository before altering the config file, it will be necessary to make changes to them or delete and add them again, in order for the new mime-type properties to be set (or to use the subcommands for editing properties, as previously mentioned).ProgrammerBrucehttp://www.blogger.com/profile/17099745653456550599noreply@blogger.com0