1

I have some objects with URLs in fields for example like this :

class A {

    val url1 : URL
    val url2 : URL
    val urls : ArrayList<URL>

}

I would like to replace URLs by Uri, but I've use Jackson and SQLite to serialise objects and store them in DB. So if I change the type, I can't retrieve stored objects.

So, I've implemented Custom Deserialise like that :

class UriDeserializer : StdDeserializer<Uri>(Uri::class.java) {

    override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Uri {

        return Uri.parse(p?.valueAsString)
    }

}

class UriArrayDeserializer : StdDeserializer<ArrayList<Uri>>(ArrayList::class.java) {

    override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): ArrayList<Uri> {

        val uriArray = arrayListOf<Uri>()

        p?.nextToken()
        while (p?.currentToken != JsonToken.END_ARRAY) {

            val uri = Uri.parse(p?.text)
            uriArray.add(uri)

            p?.nextToken()
        }

        return uriArray
    }


}

And then I've added UriDeserializer for fields in my class :

class A {

    @JsonDeserialize(using = UriDeserializer::class)
    val url1 : Uri

    @JsonDeserialize(using = UriDeserializer::class)
    val url2 : Uri

    @JsonDeserialize(using = UriArrayDeserializer::class)
    val urls : ArrayList<Uri>

}

With this code, I can deserialize my old objects, but now I can't serialise new ones, I get this error :

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: com.package.A["url2"]->android.net.Uri$StringUri["canonicalUri"])

Do I have to implement Custom Serialises too ? Maybe there is a simplest way to replace URL by Uri ?

SOLUTION

Thanks to the checked answer, I've found the solution to my problem : So I've used a MixIn interface with Custom Serializer and Deserializer.

@JsonSerialize(using = UriSerializer::class)
@JsonDeserialize(using = UriDeserializer::class)
interface UriMixIn

class UriDeserializer : StdDeserializer<Uri>(Uri::class.java) {

    override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Uri {
        return Uri.parse(p?.valueAsString)
    }
}

class UriSerializer : StdSerializer<Uri>(Uri::class.java) {

    override fun serialize(value: Uri?, gen: JsonGenerator?, provider: SerializerProvider?) {
        gen?.writeString(value?.toString())
    }
}

Then, I've added UriMixIn wherever it needed like :

val jackson = jacksonObjectMapper()
jackson.addMixIn(Uri::class.java, UriMixIn::class.java)

This solution works like a charm ! Thanks a lot for your help, have a great day !

Ady
  • 230
  • 3
  • 16

1 Answers1

2

You can implement custom serialiser or use MixIn feature.

To understand why, we need to take look how android.net.Uri class was implemented. We can check sources at Android Googlesource. Quick look and we noticed that parse method returns StringUri object:

public static Uri parse(String uriString) {
    return new StringUri(uriString);
}

It is mentioned about in internal documentation:

This class aims to do as little up front work as possible. To accomplish that, we vary the implementation depending on what the user passes in. For example, we have one implementation if the user passes in a URI string (StringUri) and another if the user passes in the individual components (OpaqueUri)

Let's analyse further. StringUri is a private static class which does not look like a regular POJO. From other side, canonicalUri field with which you have a problem returns this in case Uri is built from String so this is a reason we need a custom serialiser or using MixIn Feature we can ignore this field/getter.

This is a very common problem when Jackson is used to serialise not regular POJO objects. Similar questions:

  1. Why Jackson JSON mapping exception when Serializing/Deserializing Geometry type
  2. Serializing/deserializing exceptions without stack trace using Jackson
  3. Is there a built in mechanism within org.kohsuke.github to serialize GHRepository objects to JSON?
  4. Jackson/Gson serialize and deserialize JavaFX Properties to json
  5. Java InvalidDefinitionException when serializing object with jackson databind

Some examples, how to use MixIn:

  1. Make Jackson serializer override specific ignored fields
  2. Custom Jackson Serializer for a specific type in a particular class
  3. What is equivalent code settings for @JSonIgnore annotation?
  4. Jackson parse json with unwraping root, but without ability to set @JsonRootName

Also, you can use MixIn feature to register serialiser/deserialiser globally for Uri type. In that case you do not need to implement UriArrayDeserializer class.

See:

  1. Jackson – Custom Serializer
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 1
    Thanks a lot for your answer and your time ! If I understood correctly, I can make a custom serializer and a custom deserializer, then I use both in a MixIn interface, and then my ```jacksonObjectMapper``` can serialize/deserialize all my ```Uri``` including ```array``` ? – Ady Mar 09 '20 at 13:21
  • @Ady, yes. That's right. You need to register serializer and deserializer globally. You can do this by using `MixIn` and registering them on type level or by registering it on `ObjectMapper` level. – Michał Ziober Mar 09 '20 at 15:49