Module that adds support for serialization/deserialization of Kotlin classes and data classes.

Overview

Kotlin Kotlin Slack

Overview

Module that adds support for serialization/deserialization of Kotlin classes and data classes. Previously a default constructor must have existed on the Kotlin object for Jackson to deserialize into the object. With this module, single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported.

Status

  • release 2.13.0 (for Jackson 2.13.x) GitHub Actions build
  • release 2.12.3 (for Jackson 2.12.x) CircleCI
  • release 2.11.4 (for Jackson 2.11.x) CircleCI
  • release 2.10.5 (for Jackson 2.10.x)
  • release 2.9.10 (for Jackson 2.9.x)

Releases require that you have included Kotlin stdlib and reflect libraries already.

Gradle:

implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.+"

Maven:

<dependency>
    <groupId>com.fasterxml.jackson.modulegroupId>
    <artifactId>jackson-module-kotlinartifactId>
    <version>2.13.0version>
dependency>

Usage

For any Kotlin class or data class constructor, the JSON property names will be inferred from the parameters using Kotlin runtime type information.

To use, just register the Kotlin module with your ObjectMapper instance:

// With Jackson 2.12 and later
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
...
val mapper = jacksonObjectMapper()
// or
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
...
val mapper = ObjectMapper().registerKotlinModule()
// or
import com.fasterxml.jackson.module.kotlin.jsonMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
...
val mapper = jsonMapper {
  addModule(kotlinModule())
}
Jackson versions prior to 2.10–2.11
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
...
val mapper = JsonMapper.builder().addModule(KotlinModule()).build()
Jackson versions prior to 2.10
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
...
val mapper = ObjectMapper().registerModule(KotlinModule())

A simple data class example:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

data class MyStateObject(val name: String, val age: Int)

...
val mapper = jacksonObjectMapper()

val state = mapper.readValue<MyStateObject>(json)
// or
val state: MyStateObject = mapper.readValue(json)
// or
myMemberWithType = mapper.readValue(json)

All inferred types for the extension functions carry in full generic information (reified generics). Therefore, using readValue() extension without the Class parameter will reify the type and automatically create a TypeReference for Jackson.

Also, there are some convenient operator overloading extension functions for JsonNode inheritors.

import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.fasterxml.jackson.module.kotlin.*

// ...
val objectNode: ObjectNode = JsonNodeFactory.instance.objectNode()
objectNode.put("foo1", "bar").put("foo2", "baz").put("foo3", "bax")
objectNode -= "foo1"
objectNode -= listOf("foo2")
println(objectNode.toString()) // {"foo3":"bax"}

// ...
val arrayNode: ArrayNode = JsonNodeFactory.instance.arrayNode()
arrayNode += "foo"
arrayNode += true
arrayNode += 1
arrayNode += 1.0
arrayNode += "bar".toByteArray()
println(arrayNode.toString()) // ["foo",true,1,1.0,"YmFy"]

Annotations

You can intermix non-field values in the constructor and JsonProperty annotation in the constructor. Any fields not present in the constructor will be set after the constructor call. An example of these concepts:

   @JsonInclude(JsonInclude.Include.NON_EMPTY)
   class StateObjectWithPartialFieldsInConstructor(val name: String, @JsonProperty("age") val years: Int)    {
        @JsonProperty("address") lateinit var primaryAddress: String   // set after construction
        var createdDt: DateTime by Delegates.notNull()                // set after construction
        var neverSetProperty: String? = null                          // not in JSON so must be nullable with default
    }

Note that using lateinit or Delegates.notNull() will ensure that the value is never null when read, while letting it be instantiated after the construction of the class.

Caveats

  • The @JsonCreator annotation is optional unless you have more than one constructor that is valid, or you want to use a static factory method (which also must have platformStatic annotation, e.g. @JvmStatic). In these cases, annotate only one method as JsonCreator.
  • Serializing a member or top-level Kotlin class that implements Iterator requires a workaround, see Issue #4 for easy workarounds.
  • If using proguard:
    • kotlin.Metadata annotations may be stripped, preventing deserialization. Add a proguard rule to keep the kotlin.Metadata class: -keep class kotlin.Metadata { *; }
    • If you're getting java.lang.ExceptionInInitializerError, you may also need: -keep class kotlin.reflect.** { *; }
    • If you're still running into problems, you might also need to add a proguard keep rule for the specific classes you want to (de-)serialize. For example, if all your models are inside the package com.example.models, you could add the rule -keep class com.example.models.** { *; }

Support for Kotlin Built-in classes

These Kotlin classes are supported with the following fields for serialization/deserialization (and other fields are hidden that are not relevant):

  • Pair (first, second)
  • Triple (first, second, third)
  • IntRange (start, end)
  • CharRange (start, end)
  • LongRange (start, end)

(others are likely to work, but may not be tuned for Jackson)

Sealed classes without @JsonSubTypes

Subclasses can be detected automatically for sealed classes, since all possible subclasses are known at compile-time to Kotlin. This makes com.fasterxml.jackson.annotation.JsonSubTypes redundant. A com.fasterxml.jackson.annotation.@JsonTypeInfo annotation at the base-class is still necessary.

"It's B" }">
  @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
  sealed class SuperClass{
      class A: SuperClass()
      class B: SuperClass()
  }

...
val mapper = jacksonObjectMapper()
val root: SuperClass = mapper.readValue(json)
when(root){
    is A -> "It's A"
    is B -> "It's B"
}

Configuration

The Kotlin module may be given a few configuration parameters at construction time; see the inline documentation for details on what options are available and what they do.

val mapper = JsonMapper.builder()
        .addModule(KotlinModule(strictNullChecks = true))
        .build()

If your ObjectMapper is constructed in Java, there is a builder method provided for configuring these options:

KotlinModule kotlinModule = new KotlinModule.Builder()
        .strictNullChecks(true)
        .build();
ObjectMapper objectMapper = JsonMapper.builder()
        .addModule(kotlinModule)
        .build();

Development

Maintainers

Following developers have committer access to this project.

  • Author: Jayson Minard (@apatrida) wrote this module; still helps issues from time to time
  • Active Maintainers:
    • Drew Stephens (@dinomite)
    • Vyacheslav Artemyev (@viartemev)
  • Co-maintainers:
    • Tatu Saloranta (@cowtowncoder)

You may at-reference them as necessary but please keep in mind that all maintenance work is strictly voluntary (no one gets paid to work on this or any other Jackson components) so there is no guarantee for timeliness of responses.

All Pull Requests should be reviewed by at least one of active maintainers; bigger architectural/design questions should be agreed upon by majority of active maintainers (at this point meaning both Drew and Vyacheslav :) ).

Releases & Branches

This module follows the release schedule of the rest of Jackson—the current version is consistent across all Jackson components & modules. See the jackson-databind README for details.

Contributing

We welcome any contributions—reports of issues, ideas for enhancements, and pull requests related to either of those.

See the main Jackson contribution guidlines for more details.

Branches

If you are going to write code, choose the appropriate base branch:

  • 2.12 for bugfixes against the current stable version
  • 2.13 for additive functionality & features or minor, backwards compatible changes to existing behavior to be included in the next minor version release
  • master for significant changes to existing behavior, which will be part of Jackson 3.0

Failing tests

There are a number of tests for functionality that is broken, mostly in the failing package but a few as part of other test suites. Instead of ignoring these tests (with JUnit's @Ignore annotation) or excluding them from being run as part of automated testing, the tests are written to demonstrate the failure (either making a call that throws an exception or with an assertion that fails) but not fail the build, except if the underlying issue is fixed. This allows us to know when the tested functionality has been incidentally fixed by unrelated code changes.

See the tests readme for more information.

Comments
  • 2.12.x regression: no default no-arguments constructor found

    2.12.x regression: no default no-arguments constructor found

    I have this Kotlin object which parses an XML into a data class:

    object XmlTool {
    
        @JvmStatic
        fun parseXml(xml: String?): Product {
            val xmlIn = XMLInputFactory.newInstance()
            val factory = XmlFactory(xmlIn)
            val xmlModule = JacksonXmlModule()
    
            val mapper = XmlMapper(factory, xmlModule).registerKotlinModule()
    
            return mapper.readValue(xml, Product::class.java)
        }
    
        data class Stuff(val str: String?)
        data class Product(val stuff: Stuff?)
    }
    

    And this Java test:

    class Jackson212MissingConstructorTest {
    
        @Test
        void fails_with_jackson_2_12() throws Exception {
            String xml = "<product><stuff></stuff></product>";
    
            Product product = XmlTool.parseXml(xml);
    
            assertEquals(new Product(null), product);
        }
    }
    

    Parsing this in Jackson 2.12.0 fails with com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of jackson.xml.XmlTool$Stuff (although at least one Creator exists): no default no-arguments constructor found.

    Jackson 2.11.3 works just fine.

    The full exception is:

    jackson.xml.Jackson212MissingConstructorTest > fails_with_jackson_2_12() 
        com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `jackson.xml.XmlTool$Stuff` (although at least one Creator exists): no default no-arguments constructor found
         at [Source: (StringReader); line: 1, column: 17] (through reference chain: jackson.xml.XmlTool$Product["stuff"])
            at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
            at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1590)
            at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1215)
            at com.fasterxml.jackson.databind.deser.ValueInstantiator.createUsingDefault(ValueInstantiator.java:248)
            at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:275)
            at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.getEmptyValue(BeanDeserializerBase.java:1027)
            at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromEmptyString(StdDeserializer.java:322)
            at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:271)
            at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1480)
            at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
            at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
            at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
            at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:565)
            at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:449)
            at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1390)
            at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:362)
            at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195)
            at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
            at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4568)
            at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3523)
            at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3491)
            at jackson.xml.XmlTool.parseXml(XmlTool.kt:19)
            at jackson.xml.Jackson212MissingConstructorTest.fails_with_jackson_2_12(Jackson212MissingConstructorTest.java:14)
    

    Here's a test project: https://github.com/henrik242/jackson-xml-problem/tree/2-12-empty-constructor

    2.12 xml 
    opened by henrik242 40
  • Using Kotlin Default Parameter Values when JSON value is null and Kotlin parameter type is Non-Nullable

    Using Kotlin Default Parameter Values when JSON value is null and Kotlin parameter type is Non-Nullable

    I've got the following simplified JSON example, which I'm trying to decode into the simplified Kotlin Data Class below.

    { 
       "boolField": null,
        "stringField": null
    }
    
    data class TestObject(
            val boolField: Boolean = true,
            val stringField: String = "default"
    )
    

    The key thing here is that the Kotlin properties are not nullable, but there is a known default value for them. However, the JSON sometimes contains null for those fields.

    I am trying to get the example JSON to decode using the default values in place of the nulls since the type is non-nullable. However, this doesn't work out of the box, instead throwing a MissingKotlinParameterException.

    I had a look at modifying the code with a feature flag to behave the way I wanted. This was easy enough to do with some minor alterations to createFromObjectWith() in KotlinValueInstantiator for the String case. However, for the Boolean case it does not work, as in Java, that non-optional Boolean becomes a boolean primitive type, which cannot take null and thus Jackson Data Binding sets it with the default value of false.

    So, assuming I haven't missed the point completely with this, I'm wondering if there's a way in the KotlinValueInstantiator to know that the primitive types were set with their default values by Jackson Data Binding in order to make this work for primitive types too?

    opened by grundleborg 37
  • Boolean property name starting with 'is' not serialized/deserialized properly

    Boolean property name starting with 'is' not serialized/deserialized properly

    Hi, I'm experiencing issues with a boolean property which name starts with 'is'. While the getter doesn't get prefixed by kotlin, it seems that jackson still strips away 'is' as though it was a prefix. The resulting json doesn't contain the 'is', and deserialization fails.

    This test illustrates the problem:

    class SerializationTest {
        @Test
        fun testIsBool() {
            val mapper = ObjectMapper()
            mapper.registerModule(KotlinModule())
    
            val example = IsBoolExample(true)
            val json = mapper.writeValueAsString(example) 
            // json contains: {"trueOrFalse":true}
    
            val deserialized = mapper.readValue(json, IsBoolExample::class.java)
    
            Assert.assertEquals(example.isTrueOrFalse, deserialized.isTrueOrFalse)
        }
    }
    class IsBoolExample(val isTrueOrFalse: Boolean)
    

    This fails on mapper.readValue(..) with:

    com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.bol.service.reptile.spring.IsBoolExample: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?) at [Source: {"trueOrFalse":true}; line: 1, column: 2]

    is-prefix 
    opened by tbvh 36
  • MissingKotlinParameterException: due to missing (therefore NULL) value for creator parameter … which is a non-nullable type

    MissingKotlinParameterException: due to missing (therefore NULL) value for creator parameter … which is a non-nullable type

    We ran into the following issue when we tried to deserialize a JSON string coming from our Spring rest controller.

     FATAL EXCEPTION: main
        Process: com.example.driver, PID: 28305
        java.lang.IllegalStateException: Failed to load DataModel: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.example.model.StorageDevice] value failed for JSON property storageDeviceId due to missing (therefore NULL) value for creator parameter storageDeviceId which is a non-nullable type
            at [Source: (okhttp3.ResponseBody$BomAwareReader); line: 1, column: 1764] (through reference chain: com.example.model.DataModel["storageDevices"]->java.util.ArrayList[0]->com.example.model.cargospace.StorageDevice["storageDeviceId"])
            at com.example.model.RestTourRepository$updateDataModel$1.onFailure(RestTourRepository.kt:38)
            at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$2.run(ExecutorCallAdapterFactory.java:79)
            at android.os.Handler.handleCallback(Handler.java:751)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:154)
            at android.app.ActivityThread.main(ActivityThread.java:6121)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
    

    The object we try to construct looks like this:

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator::class,
            property = "storageDeviceId"
    )
    data class StorageDevice (
        val storageDeviceId: String,
        val configuration: AbstractStorageDeviceConfiguration,
        val storageDeviceLocation: StorageDeviceLocation
    )
    

    And the JSON string we receive is the following:

    {
        "storageDeviceId": "71ba49f3-d8c8-425f-b7e4-29b8c49f417d",
        "id": 166,
        "configuration": {
            "storageDeviceConfigurationId": "c2c926b3-de70-491a-9f8d-514830d56a4b",
             …
        },
        "storageDeviceLocation": "1f64613b-6e96-4825-a489-faa00608516e",
        "new": false
    }, …
    

    We solved the issue by changing the val storageDeviceId: String to val storageDeviceId: String?. The value was then properly set after deserialization.

    The question arises why we had to set it to nullable in the first place?

    Versions: jackson-module-kotlin:2.9.0 jackson-annotations:2.9.0

    opened by steffenkolb 27
  • Serialization doesn't work correctly for objects parametrized by inline class

    Serialization doesn't work correctly for objects parametrized by inline class

    import com.fasterxml.jackson.databind.ObjectMapper
    import com.fasterxml.jackson.module.kotlin.KotlinModule
    
    inline class A(val value: Int = 0)
    inline class B(val value: Int = 0)
    inline class C(val value: Int = 0)
    class D(val inlineField: A = A())
    
    class Pojo(
        val works: A,
        val alsoWorks: B,
        val worksToo: Collection<D>,
        val broken: Collection<C>
    )
    
    val om = ObjectMapper().registerModule(KotlinModule())
    println(om.writeValueAsString(Pojo(A(), B(), listOf(D(), D()), listOf(C(), C()))))
    

    Output:

    {
      "works": 0,
      "alsoWorks": 0,
      "worksToo": [ {"inlineField":0}, {"inlineField":0} ],
      "broken": [ {"value":0}, {"value":0} ]
    }
    

    Jackson version 2.12

    opened by sanyarnd 25
  • @JsonProperty annoation not respected for booleans in constructor.

    @JsonProperty annoation not respected for booleans in constructor.

    Hello,

    The @JsonProperty annotation doesn't work as expected for Boolean properties.

    • When used on a constructor property, it doesn't work
    • When used on a class property, it works but the original property name is serialized too.

    See example below:

    fun test() {
        val mapper = jacksonObjectMapper()
    
        println(mapper.writeValueAsString(Test()))
        
        //output: {"is_lol":"sdf","foo":true,"bar":true,"is_foo":true}
    }
    
    class Test(
            // This gets serialized as "is_lol", as expected. No issues here.
            @JsonProperty("is_lol")
            val lol: String = "sdf",
    
            // This gets serialized as "bar", even though we asked for "is_bar". This is an issue.
            @JsonProperty("is_bar")
            val isBar: Boolean = true) {
    
        // This gets serialized as both "foo" and "is_foo", although we only asked for "is_foo". Also an issue.
        @JsonProperty("is_foo")
        val isFoo: Boolean = true
    }
    

    EDIT: It works if we use @get:JsonProperty()

    So in the example above, using @get:JsonProperty("is_bar") gives me the correct results.

    Therefore, maybe close this ticket but improve README by mentioning this?

    opened by gregschlom 25
  • Default values for primitive parameters

    Default values for primitive parameters

    I was surprised by the behavior of Jackson+Kotlin with respect to primitive parameters that have default values. It seems like those default values are ignored, and zero is used instead.

    Here's an example:

    class Main private constructor() {
      compainion object {
        @JvmStatic fun main(args: Array<String>) {
          val mapper = ObjectMapper().registerModule(KotlinModule())
          val string1 = "{\"i\":3}"
          val string2 = "{}"
          val value1 = mapper.readValue(string1, Foo::class.java)
          val value2 = mapper.readValue(string2, Foo::class.java)
          println("value1: $value1")
          println("value2: $value2")
        }
      }
    
      data class Foo
      @JsonCreator constructor(@JsonProperty val i: Int = 5)
    }
    

    That prints:

    value1: Foo(i=3)
    value2: Foo(i=0)
    

    But I wanted it to print:

    value1: Foo(i=3)
    value2: Foo(i=5)
    

    Is that beyond the scope of this module? Is it impossible for some reason?

    opened by michaelhixson 23
  • Fatal Exception: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType

    Fatal Exception: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType

    Fatal Exception: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation; in class Ljava/lang/reflect/Field; or its super classes (declaration of 'java.lang.reflect.Field' appears in /system/framework/core-libart.jar)
           at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.hasRequiredMarker(KotlinAnnotationIntrospector.kt:45)
           at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.access$hasRequiredMarker(KotlinAnnotationIntrospector.kt:23)
           at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector$hasRequiredMarker$1.invoke(KotlinAnnotationIntrospector.kt:32)
           at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector$hasRequiredMarker$1.invoke(KotlinAnnotationIntrospector.kt:23)
           at com.fasterxml.jackson.module.kotlin.ReflectionCache.javaMemberIsRequired(KotlinModule.kt:92)
           at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.hasRequiredMarker(KotlinAnnotationIntrospector.kt:26)
           at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:307)
           at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:307)
           at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4.withMember(POJOPropertyBuilder.java:655)
           at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4.withMember(POJOPropertyBuilder.java:652)
           at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.fromMemberAnnotations(POJOPropertyBuilder.java:1154)
           at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder._findRequired(POJOPropertyBuilder.java:652)
           at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.getMetadata(POJOPropertyBuilder.java:220)
           at com.fasterxml.jackson.databind.deser.SettableBeanProperty.<init>(SettableBeanProperty.java:137)
           at com.fasterxml.jackson.databind.deser.impl.FieldProperty.<init>(FieldProperty.java:46)
           at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.constructSettableProperty(BeanDeserializerFactory.java:803)
           at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:520)
           at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:228)
           at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:137)
           at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:411)
           at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
           at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
           at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
           at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
           at com.fasterxml.jackson.databind.DeserializationContext.findNonContextualValueDeserializer(DeserializationContext.java:467)
           at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:473)
           at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
           at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
           at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
           at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:477)
           at com.fasterxml.jackson.databind.ObjectReader._prefetchRootDeserializer(ObjectReader.java:1938)
           at com.fasterxml.jackson.databind.ObjectReader.<init>(ObjectReader.java:189)
           at com.fasterxml.jackson.databind.ObjectMapper._newReader(ObjectMapper.java:658)
           at com.fasterxml.jackson.databind.ObjectMapper.readerFor(ObjectMapper.java:3518)
           at com.garmin.android.apps.gccm.api.converter.JacksonConverterFactory.responseBodyConverter(JacksonConverterFactory.java:72)
           at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:328)
           at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:311)
           at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:68)
           at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:46)
           at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:36)
           at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:168)
           at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
           at java.lang.reflect.Proxy.invoke(Proxy.java:393)
    

    version 2.9.8

    I think this error is bron on version 2.9.7 , I have tested it on different android version and it is only crash below android 7.0 . it`s means that if android os version is 7.0+ this library is work good if not the error of above will shown.

    opened by JeckChou 21
  • Honor nullability semantics of collection types

    Honor nullability semantics of collection types

    This partially addresses the issue in Github 27 that I also encountered. For my specific case, the issue was that I had a property typed as something like List (so non-nullable), but no error would be thrown during deserialization if you passed in something like [null]. Instead, later on after deserialization you would get a somewhat cryptic NPE like exception when you tried to access an element in the list. There is some performance impact to this solution so I understand if you'd prefer to wrap this in some option that has to be enabled. Although we have been using these changes in a local fork for months without any noticeable issues and would be willing to tolerate a minor performance hit to avoid the above mentioned errors. This issue does not address nullability of a list of generics as that is a harder problem to solve.

    opened by DavidRigglemanININ 21
  • Broken Kotlin 1.4 support in 2.13.2

    Broken Kotlin 1.4 support in 2.13.2

    Actual

    Using Kotlin 1.4.32 and Jackson 2.13.2.20220324 with jackson-module-kotlin I'm getting an exception

    Caused by: java.lang.NoSuchMethodError: 'boolean kotlin.reflect.KClass.isValue()'
    	at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.findSerializer(KotlinAnnotationIntrospector.kt:88)
    	at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.findSerializer(KotlinAnnotationIntrospector.kt:26)
    	at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.findSerializer(AnnotationIntrospectorPair.java:334)
    	at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.findSerializer(AnnotationIntrospectorPair.java:334)
    	at com.fasterxml.jackson.databind.ser.BasicSerializerFactory.findSerializerFromAnnotation(BasicSerializerFactory.java:540)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._constructWriter(BeanSerializerFactory.java:862)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanProperties(BeanSerializerFactory.java:630)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.constructBeanOrAddOnSerializer(BeanSerializerFactory.java:401)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanOrAddOnSerializer(BeanSerializerFactory.java:294)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:239)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:173)
    	at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1495)
    	at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1443)
    	at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:544)
    	at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:822)
    	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:308)
    	at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4568)
    	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsBytes(ObjectMapper.java:3844)
    

    The same code works fine with Jackson 2.13.1

    The regression is introduced in https://github.com/FasterXML/jackson-module-kotlin/pull/527 kotlin.reflect.KClass.isValue() was added in Kotlin 1.5 and not available in 1.4: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/is-value.html

    Expected

    • get Kotlin 1.4 support back (the last release of 1.4 was not that long time ago to consider it end of life https://github.com/JetBrains/kotlin/releases/tag/v1.4.32) or
    • explicitly state which versions of kotlin are supported by this module and that Kotlin 1.4 got dropped in 2.13.2. Jackson Kotlin Module's release notes are stating: "No changes since 2.13.1?" for 2.13.2.
    bug 
    opened by Spikhalskiy 20
  • Honor nullability semantics of lists, arrays, and maps

    Honor nullability semantics of lists, arrays, and maps

    I wasn't sure if I was supposed to create these changes off of master or the 2.10 branch (since I was told "Update any work to be current with the 2.10 branch", so for now, I created one off the 2.10 branch. Let me know for future PRs if I should be branching off master instead. I'm not sure what your workflow is for maintaining multiple branches and releases.

    opened by DavidRigglemanININ 19
  • Another binary compatibility issue in 2.13

    Another binary compatibility issue in 2.13

    I have an internal library written a while ago and compiled against 2.9.10 version. And an application that uses this library. Everything works smooth when the application has 2.12.6 on the classpath. But I'm getting an error when launching with 2.13.4:

    java.lang.NoSuchMethodError: 'void com.fasterxml.jackson.module.kotlin.KotlinModule.<init>(int, boolean, boolean, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker)'
    

    Basically the library was complied against this constructor signature from 2.9.10:

    class KotlinModule(val reflectionCacheSize: Int = 512, val nullToEmptyCollection: Boolean = false, val nullToEmptyMap: Boolean = false)
    

    having the bytecode:

    public synthetic <init>(IZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
    

    Next in 2.12.6 it looks like:

    @Deprecated(level = DeprecationLevel.HIDDEN, message = "For ABI compatibility")
    constructor(
        reflectionCacheSize: Int = 512,
        nullToEmptyCollection: Boolean = false,
        nullToEmptyMap: Boolean = false,
        nullIsSameAsDefault: Boolean = false
    )
    

    that still yields the same bytecode signature when compiling.

    In 2.13 version in the PR #454 it was amended. Note the default values being dropped:

    @Deprecated(level = DeprecationLevel.HIDDEN, message = "For ABI compatibility")
    constructor(
        reflectionCacheSize: Int,
        nullToEmptyCollection: Boolean,
        nullToEmptyMap: Boolean,
        nullIsSameAsDefault: Boolean
    ) 
    

    So that its bytecode signature is now:

    public synthetic <init>(IZZZ)V
    

    Hence the NoSuchMethodError from the beginning.

    bug 
    opened by ilya-yamschikov 0
  • Type Introspection: non-nullable field with default is reported as required

    Type Introspection: non-nullable field with default is reported as required

    Describe the bug

    Type-Introspection of a data class reports its property as "required" though it has a default value. The reason - at least in my case - is that KotlinAnnotationIntrospector#requiredAnnotationOrNullability favors "required by nullability" over whatever the JsonProperty annotation says.

    To Reproduce

    data class TestData(
        @JsonProperty(value = "prop", required = false, defaultValue = "250")
        val someproperty: Int = 250
    )
    
    @Test
    fun expectSomePropertyToNotBeRequired() {
        val javaType = mapper.constructType(TestData::class.java)
        val beandescription = mapper.getSerializationConfig().introspect(javaType)
    
        val def = beandescription.findProperties().first()
        
        // fails as the field is reported as required
        assert(!def.metadata.required, { "Property is expected to not be required" })
    }
    

    Expected behavior

    It would be nice if the introspection could consider the propertys' default-value; however I would expect to overrule its decisions by being explicit via the JsonProperty-Annotation.

    Versions Kotlin: 1.7.0 Jackson-module-kotlin: 2.13.4 Jackson-databind: 2.13.4.2

    Additional context

    Frameworks that are using Jacksons introspection on Kotlin classes will come to wrong conclusions (as seen in swagger.io which produces kind of wrong schemas). Something similiar has been reported in issue #397

    bug 
    opened by pepperbob 0
  • Value class incompatible with Jackson default typing

    Value class incompatible with Jackson default typing

    Describe the bug

    Value class throw exception when Jackson default typing is activated

    To Reproduce

    data class MyDataClass(val myValue: Any)
    
    @JvmInline
    value class MyValueClass(val myValue: String)
    
    val objectMapper = ObjectMapper().apply {
        activateDefaultTyping(polymorphicTypeValidator, DefaultTyping.NON_FINAL, As.PROPERTY)
        registerKotlinModule()
    }
    
    objectMapper.writeValueAsString(MyDataClass(MyValueClass("my value")))
    

    Output :

    Type id handling not implemented for type java.lang.Object (by serializer of type com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer) (through reference chain: fr.maif.epa.backmobile.commons.infrastructure.cache.CacheServiceTest$MyDataClass["myValue"])
    com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Type id handling not implemented for type java.lang.Object (by serializer of type com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer) (through reference chain: fr.maif.epa.backmobile.commons.infrastructure.cache.CacheServiceTest$MyDataClass["myValue"])
    	at app//com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
    

    Expected behavior

    Jackson module Kotlin should be able de serialize/deserialize this kind of value with Jackson default typing

    Versions

    Kotlin: 1.7.0 Jackson-module-kotlin: 2.13.3 Jackson-databind: 2.13.3

    Additional context

    Model used to reproduce is a simplified one, just to put in light the current behavior (remove sealed class, complex type in value class etc...).

    bug 
    opened by christophejan 0
  • Strange serialization property name `internal`

    Strange serialization property name `internal`

    Hi experts, I am new to Kotlin and I am trying to use Jackson for a simple (de)serialization scenario of a Kotlin class with the Jackson Kotlin module. However when I serialize the object I see a strange property pattern for the high-level object. For example if my class is called Account and it has a property transactions instead of "transactions:[]" in the json output I see transactions$":[]. Why is that ? I presume it is pretty straightforward to answer. Thanks in advance!

    question 
    opened by entrery 5
  • ObjectMapper.registerKotlinModule does not works

    ObjectMapper.registerKotlinModule does not works

    Describe the bug After I create a Jackson2JsonRedisSerializer with ObjectMapper using registerKotlinModule in com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0, a Kotlin data class cannot be serialized and deserialized successfully. It throws an exception when deserializing: org.springframework.data.redis.serializer.SerializationException, whose message is Could not read JSON: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

    To Reproduce

    data class Foo(val bar: Int)
    class SerializerTestcase {
        @Test
        fun testcase1() {
            // Initialize serializer
            val serializer = Jackson2JsonRedisSerializer(Any::class.java)
            val objectMapper = ObjectMapper().registerKotlinModule()
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
            objectMapper.activateDefaultTyping(objectMapper.polymorphicTypeValidator, ObjectMapper.DefaultTyping.NON_FINAL)
            serializer.setObjectMapper(objectMapper)
            // Exception demo
            val input = Foo(0)
            println(input) // It prints: Foo(bar=0)
            val bytes = serializer.serialize(input)
            println(bytes.toString(StandardCharsets.UTF_8)) // It prints: {"bar":0}
            val output = serializer.deserialize(bytes) // An exception occurred here:
            // org.springframework.data.redis.serializer.SerializationException:
            // Could not read JSON: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object
            println(output)
        }
    }
    

    Expected behavior It ought to be deserialized successful of the string which is serialized by this module as well.

    Versions Kotlin:1.7.21 Jackson-module-kotlin:2.14.0 Jackson-databind:2.14.0

    Additional context When I using the same serializer to serialize and deserialize a Java class, it works. For example,

    // This is a Java class
    public class Bar {
        private int foo;
        public Bar(int foo) { this.foo = foo; }
    }
    
    // This is a same code as above
    class SerializerTestcase {
        @Test
        fun testcase2() {
            // Initialize serializer
            val serializer = Jackson2JsonRedisSerializer(Any::class.java)
            val objectMapper = ObjectMapper().registerKotlinModule()
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
            objectMapper.activateDefaultTyping(objectMapper.polymorphicTypeValidator, ObjectMapper.DefaultTyping.NON_FINAL)
            serializer.setObjectMapper(objectMapper)
            val input = Bar(0)
            val bytes = serializer.serialize(input)
            println(bytes.toString(StandardCharsets.UTF_8)) // It prints: ["org.example.Bar",{"foo":0}]
            val output = serializer.deserialize(bytes)
            println(output)
        }
    }
    

    It can be argued that the serialized string does not have a same json format, will it be where the exception occurred?

    bug 
    opened by Zhou-Tx 0
  • CSV EMPTY_STRING_AS_NULL with non-nullable default value = exception

    CSV EMPTY_STRING_AS_NULL with non-nullable default value = exception

    Describe the bug I have a csv with empty cells. I generally want those to be parsed as null. However, I want to also have the ability to parse these cells into non-nullable properties by defining a default value in the constructor. Unfortunately, this leads to an exception:

    Instantiation of [simple type, class mypackage.Foo] value failed for JSON property bar due to missing (therefore NULL) value for creator parameter bar which is a non-nullable type
     at [Source: (StringReader); line: 2, column: 2] (through reference chain: mypackage.Foo["bar"])
    com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class mypackage.Foo] value failed for JSON property bar due to missing (therefore NULL) value for creator parameter bar which is a non-nullable type
     at [Source: (StringReader); line: 2, column: 2] (through reference chain: mypackage.Foo["bar"])
    	at app//com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:84)
    	at app//com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:202)
    	at app//com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:444)
    	at app//com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405)
    	at app//com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
    	at app//com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
    	at app//com.fasterxml.jackson.databind.MappingIterator.nextValue(MappingIterator.java:283)
    	at app//com.fasterxml.jackson.databind.MappingIterator.readAll(MappingIterator.java:323)
    	at app//com.fasterxml.jackson.databind.MappingIterator.readAll(MappingIterator.java:309)
    

    To Reproduce

    data class Foo(val bar: String = "default", val baz: String)
    
    class EmptyStringAsNullIntoNunNullableParameterWithDefaultValue {
        @Test
        internal fun test() {
            val parsed = CsvMapper()
                .registerModule(KotlinModule())
                .readerFor(Foo::class.java)
                .withFeatures(CsvParser.Feature.EMPTY_STRING_AS_NULL)
                .with(CsvSchema.emptySchema().withHeader())
                .readValues<Foo>(
                    """
                    bar,    baz
                    ,       42                
                """.trimIndent()
                ).readAll()
            println(parsed)
        }
    }
    

    Expected behavior It can be argued that a json like {"bar":null, "baz":"42"} should produce an error when parsed into Foo, because there is an explicit null.

    However, in the case of CSV, there is no such thing as "explicit null". The value is only null because of CsvParser.Feature.EMPTY_STRING_AS_NULL. Therefore, I would expect the above test not to fail. KotlinValueInstantiator should probably handle this case by setting isMissing = true, which would likely fix the problem (I did not test this, I only had a brief look at the code).

    Versions Kotlin: 1.7.20 Jackson-module-kotlin: 2.14.0 Jackson-databind: 2.14.0

    bug 
    opened by hosswald 5
Owner
FasterXML, LLC
FasterXML, LLC
Android Parcelable support for the Kotlinx Serialization library.

Android Parcelable support for the Kotlinx Serialization library.

Christopher 50 Nov 20, 2022
Adds emoji support to the Minecraft chat.

WeirdChat ?? WeirdChat is a FabricMC mod that adds Emoji support to the Minecraft chat. Instead of completely rewriting the text renderer, Discord emo

Sarah 2 Oct 30, 2022
Create an application with Kotlin/JVM and Kotlin/JS, and explore features around code sharing, serialization, server- and client

Practical Kotlin Multiplatform on the Web 본 저장소는 코틀린 멀티플랫폼 기반 웹 프로그래밍 워크숍(강좌)을 위해 작성된 템플릿 프로젝트가 있는 곳입니다. 워크숍 과정에서 코틀린 멀티플랫폼을 기반으로 프론트엔드(front-end)는 Ko

SpringRunner 14 Nov 5, 2022
Create an application with Kotlin/JVM and Kotlin/JS, and explore features around code sharing, serialization, server- and client

Building a Full Stack Web App with Kotlin Multiplatform 본 저장소는 INFCON 2022에서 코틀린 멀티플랫폼 기반 웹 프로그래밍 핸즈온랩을 위해 작성된 템플릿 프로젝트가 있는 곳입니다. 핸즈온 과정에서 코틀린 멀티플랫폼을

Arawn Park 19 Sep 8, 2022
Kotlin tooling for generating kotlinx.serialization serializers for serializing a class as a bitmask

kotlinx-serialization-bitmask Kotlin tooling for generating kotlinx.serialization serializers for serializing a class as a bitmask. Example @Serializa

marie 2 May 29, 2022
CSV and FixedLength Formats for kotlinx-serialization

Module kotlinx-serialization-csv Serialize and deserialize ordered CSV and Fixed Length Format Files with kotlinx-serialization. Source code Docs Inst

Philip Wedemann 12 Dec 16, 2022
Clean MVVM with eliminating the usage of context from view models by introducing hilt for DI and sealed classes for displaying Errors in views using shared flows (one time event), and Stateflow for data

Clean ViewModel with Sealed Classes Following are the purposes of this repo Showing how you can remove the need of context in ViewModels. I. By using

Kashif Mehmood 22 Oct 26, 2022
Type-safe arguments for JetPack Navigation Compose using Kotlinx.Serialization

Navigation Compose Typed Compile-time type-safe arguments for JetPack Navigation Compose library. Based on KotlinX.Serialization. Major features: Comp

Kiwi.com 32 Jan 4, 2023
KotlinX Serialization Standard Serializers (KS3)

KotlinX Serialization Standard Serializers (KS3) This project aims to provide a set of serializers for common types. ⚠️ Consider this project to be Al

Emil Kantis 3 Nov 5, 2022
Course_modularizing_android_apps - Multi-module demo app that gets data from a Dota2 api

Work in progress Multi-module demo app that gets data from a Dota2 api. Module n

Julio Ribeiro 1 Dec 30, 2021
Adds loveable red pandas into Minecraft! Who doesn't love a Red Panda in their world?

?? Akali Adds loveable red pandas into Minecraft! Who doesn't love a Red Panda in their world? :3 This is a Forge and Fabric mod under their respected

Noel ʕ •ᴥ•ʔ 4 Jul 4, 2022
WaxedNotWaxed - Adds a simple indicator to know if a copper block is waxed or not

Waxed Not Waxed Adds a simple indicator to know if a copper block is waxed or no

Mateusz 2 Nov 11, 2022
Sample application to demonstrate Multi-module Clean MVVM Architecture and usage of Android Hilt, Kotlin Flow, Navigation Graph, Unit tests etc.

MoneyHeist-Chars Sample application to demonstrate Multi-module Clean MVVM Architecture and usage of Android Hilt, Kotlin Flow, Navigation Graph, Room

Hisham 20 Nov 19, 2022
Workout Journal is a mobile app based on Multi-Module and Clean Architecture for those who want to track their progress over a workout and a calendar period.

Workout-Journal Workout Journal is a mobile app for those who want to track their progress over a workout and a calendar period. The app allows you to

Maxim Smolyakov 4 Oct 23, 2022
[prototype] Generate TypeScript interfaces from Kotlin classes

Kotlinx Serialization TypeScript Generator Kotlinx Serialization TypeScript Generator creates TypeScript interfaces from Kotlinx Serialization classes

null 17 Dec 18, 2022
A set of highly-opinionated, batteries-included gradle plugins to get you started building delicious multi-module Kotlin projects

Sourdough Gradle What is Sourdough Gradle? Sourdough is a set of highly opinionated gradle plugins that aim to act as the starter for your Kotlin proj

Backbone 0 Oct 3, 2022
🚧 General-Purpose Module System for Kotlin.

?? Modules: General-Purpose Module System A module system & loader for Kotlin. Made for me to use. Architecture Module is a building block for this sy

lhwdev 0 Dec 29, 2021
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.

Themis provides strong, usable cryptography for busy people General purpose cryptographic library for storage and messaging for iOS (Swift, Obj-C), An

Cossack Labs 1.6k Jan 8, 2023