Moshi: @Embed part of the json

Created on 19 Nov 2019  路  7Comments  路  Source: square/moshi

Is there something like this already available?

If not, do you think it is feasible with a customer JsonQualifier annotation?

{
  "id": "abc",
  "name": "example",
  "other": "foo"
}

And parse this with:

interface Base {
  val id: String
  val name: String
}

@JsonClass(generateAdapter = true)
data class BaseImpl(
  override val id: String,
  override val name: String
): Base

@JsonClass(generateAdapter = true)
data class MyType(
  漏JsonEmbed(BaseImpl::class)
  private val base: Base,
  val other: String
): Base by base

I've several models with common parts and I'm looking for ways to reduce the code repetition through the codebase by splitting my models into smaller classes and sharing them like the example above.

Thanks

Most helpful comment

I don't have a different set of objects for each id, id are actually UUID autogenerated, that was an example.

i want moshi to parse my smaller objects (Entity, InternalMetadata, flat inside the bigger object).

I don't need polymorphic stuff. when i parse a SomeComplexObject i know i'm parsing that.
It's simply composition, AnotherComplexObject share stuff with SomeComplexObject but they are 2 different things.

{
   "uuid": "cf85b6ec-4511-488d-a058-b2ca869eb21e",
   "name": "This is a SomeComplexObject",
   "description": "And is a composition of 2 smaller parts shared with other objects and some stuff which is specific to it",
   "image": "https://......",
   "content": {
   "someOtherStuffSpecificToThis": "this is something only this kind of object have",
   "created": "2019-11-30T07:51:29Z",
   "lastUpdate": "2019-11-30T07:51:29Z",
}
````

and the other type:

{
"uuid": "b7f82470-295d-4203-ace7-89b197ce8506",
"name": "This is an AnotherComplexObject",
"description": "And is a composition of 3 smaller parts shared with other objects",
"image": "https://......",
"content": {
"en": { ... },
"it": { ... },
},
"fallbackLocale": "en",
"created": "2019-11-30T07:51:29Z",
"lastUpdate": "2019-11-30T07:51:29Z",
}
```

I don't need polymorphism, i just need a way to define some attributes of the json into smaller pieces and tell moshi to parse them embedded into the json.

All 7 comments

You can do this like so:

val moshi = Moshi.Builder()
    .add(object : JsonAdapter.Factory {
      override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*> {
        return if (type == Base::class.java) return moshi.adapter(BaseImpl::class.java) else null
      }
    })

If your type can have different subtypes, you can use the PolymorphicJsonAdapter for this use case

val moshi = Moshi.Builder()
       .add(PolymorphicJsonAdapterFactory.of(Base::class.java, "id")
           .withSubtype(BaseImpl::class.java, "<yourIdForThisHere>"))
       .build()

I do wonder if we should maybe have something similar to Dagger's "binds" semantic, where Moshi.Builder could have a method to specify something like this

public Builder bind(Type from, Type to) {
  // Check assignability?
  // ...
}

That could be used to define a sort of default binding between simple types. Thoughts @swankjesse @rharter @JakeWharton?

Hum,

I guess my Base example wasn't good enough.

I know about the PolymorphicJsonAdapterFactory, but that is NOT what I want.

I want to be able to define complex structures by smaller objects that I combine with the by kotlin keyword.
Basically writing code and pojo by composition.

say

interface Entity {
    val uuid: String
    val created: Date
    val lastUpdate: Date
}

interface InternalMetadata {
    val name: String
    val description: String
    val image: String
}

interface I18N {
    val content: Map<Locale, Translation>
    val fallbackLocale: Locale
}

// etc...

// some example
@JsonClass(generateAdapter = true)
data class SomeComplexObject (
    @EmbedJson
    private val entity: Entity,
    @EmbedJson
    private val metas: InternalMetadata,
    val someOtherStuffSpecificToThis: String
): Entity by entity, InternalMetadata by metas

// another example
@JsonClass(generateAdapter = true)
data class AnotherComplexObject (
    @EmbedJson
    private val entity: Entity,
    @EmbedJson
    private val metas: InternalMetadata,
    @EmbedJson
    private val i18n: I18N 
): Entity by entity, InternalMetadata by metas, I18N by i18n

all these JSON are supposed to be flat, the usage is also gonna feel flat, I just want to be able to combine smaller pieces like this to more easily share code and part of the behavior.

It seems like the first example I gave you (the custom factory that links interfaces to implementations) is what you want? I don't see why that doesn't work for your case at least.

I don't have a different set of objects for each id, id are actually UUID autogenerated, that was an example.

i want moshi to parse my smaller objects (Entity, InternalMetadata, flat inside the bigger object).

I don't need polymorphic stuff. when i parse a SomeComplexObject i know i'm parsing that.
It's simply composition, AnotherComplexObject share stuff with SomeComplexObject but they are 2 different things.

{
   "uuid": "cf85b6ec-4511-488d-a058-b2ca869eb21e",
   "name": "This is a SomeComplexObject",
   "description": "And is a composition of 2 smaller parts shared with other objects and some stuff which is specific to it",
   "image": "https://......",
   "content": {
   "someOtherStuffSpecificToThis": "this is something only this kind of object have",
   "created": "2019-11-30T07:51:29Z",
   "lastUpdate": "2019-11-30T07:51:29Z",
}
````

and the other type:

{
"uuid": "b7f82470-295d-4203-ace7-89b197ce8506",
"name": "This is an AnotherComplexObject",
"description": "And is a composition of 3 smaller parts shared with other objects",
"image": "https://......",
"content": {
"en": { ... },
"it": { ... },
},
"fallbackLocale": "en",
"created": "2019-11-30T07:51:29Z",
"lastUpdate": "2019-11-30T07:51:29Z",
}
```

I don't need polymorphism, i just need a way to define some attributes of the json into smaller pieces and tell moshi to parse them embedded into the json.

My request is basically the same you can see here in Room database: https://developer.android.com/reference/android/arch/persistence/room/Embedded

just for Json / Moshi

@ZacSweers Sorry to ask here but this is the 'best' resource I have found and I've searched deep and wide...
So how to actually 'inline' (or embed) the object and use a codegen to help you do so? I think that was the original author's intent.

I am interested in writing something like this :

interface InnerFields{
  val innerString1:String,
   val innerString2:String
}
data class Inner(
   val innerString1:String,
   val innerString2:String
) :InnerFields

data class Outer(
    val inlineThis:Inner,
    val outerString1:String,
    val outerString2:String
): InnerFields by inlineThis

Result (without values, to cut back on my own boilderplate example...):
{ "innerString1" : "..." , "innerString2" : "...", "outerString1" : " ...", "outerString2" : "..." }

Is it possible to write custom adapter that will 'know' that some objects should be flattened, without handling each 'parent' object separately, probably via using a JsonWriter.beginFlattern()? If it's any easier I only need a depth of one inner object ..

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mleonhard picture mleonhard  路  4Comments

rephiscorth picture rephiscorth  路  5Comments

PaulWoitaschek picture PaulWoitaschek  路  4Comments

stefanmedack picture stefanmedack  路  4Comments

tevjef picture tevjef  路  3Comments