Moshi: Parse single object or list of objects (kotlin)
I am building an app which can fetch a Warning
object from an API which I do not control nor do they want to remove this behaivor to which I am trying to work around.
I would like to be able to parse either a single Warning
object or a List<Warning>
objects. (Because the API returns either 1 object if there is only 1 or > 1 a list of objects.)
Question
How can I parse an object as a list directly or parse list this with Moshi?
The data I get looks either like this:
{
# List<Warning>
"warnings": [{...}, {...}]
}
or like this:
{
# Warning
"warnings": {...}
}
I have read and found some sources on this topic, but no examples and I am not sure how to proceed.. so any pointers would be highly appreciated.
Attempt
I initially tried to shoehorn an autogenerated moshi adapter thinking I might be able to build ontop of it but I couldn't figure out what was going on so it didn't really lead me anywhere. But I included the generated code anyway incase it might be useful for the task still.
Edit: This is what I currently have, though im not very pleased by the fact it takes an instance of Moshi to work.
Solution
Generalised approach with a Factory
I tried to port adapter which Eric wrote to kotlin
since I since has realised that a more general approach is better much like
Eric points out in his reply. As soon a this is working, I will re-write parts of this post to make it more understandable, its a bit messy now I apologize for that.
EDIT: I ended up using the solution Eric suggested in another thread but ported to Kotlin.
Adapter with Factory
class SingleToArrayAdapter(
val delegateAdapter: JsonAdapter<List<Any>>,
val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {
companion object {
val INSTANCE = SingleToArrayAdapterFactory()
}
override fun fromJson(reader: JsonReader): Any? =
if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) {
Collections.singletonList(elementAdapter.fromJson(reader))
} else delegateAdapter.fromJson(reader)
override fun toJson(writer: JsonWriter, value: Any?) =
throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects")
class SingleToArrayAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<Any>? {
val delegateAnnotations = Types.nextAnnotations(annotations, SingleToArray::class.java)
if (delegateAnnotations == null) return null
if (Types.getRawType(type) != List::class.java) throw IllegalArgumentException("Only lists may be annotated with @SingleToArray. Found: $type")
val elementType = Types.collectionElementType(type, List::class.java)
val delegateAdapter: JsonAdapter<List<Any>> = moshi.adapter(type, delegateAnnotations)
val elementAdapter: JsonAdapter<Any> = moshi.adapter(elementType)
return SingleToArrayAdapter(delegateAdapter, elementAdapter)
}
}
}
Qualifier
Note: I had to add the Target(FIELD)
.
@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray
Usage
Annotate a field you want to make sure is parsed as a list with @SingleToArray
.
data class Alert(
@SingleToArray
@Json(name = "alert")
val alert: List<Warning>
)
and dont forget to add the adapter to your moshi instance:
val moshi = Moshi.Builder()
.add(SingleToArrayAdapter.INSTANCE)
.build()
Related issues/topics:
- Parse JSON key that is either object or array of object
- Moshi Determine if JSON is array or single object
- https://github.com/square/moshi
android json kotlin adapter moshi
add a comment |
I am building an app which can fetch a Warning
object from an API which I do not control nor do they want to remove this behaivor to which I am trying to work around.
I would like to be able to parse either a single Warning
object or a List<Warning>
objects. (Because the API returns either 1 object if there is only 1 or > 1 a list of objects.)
Question
How can I parse an object as a list directly or parse list this with Moshi?
The data I get looks either like this:
{
# List<Warning>
"warnings": [{...}, {...}]
}
or like this:
{
# Warning
"warnings": {...}
}
I have read and found some sources on this topic, but no examples and I am not sure how to proceed.. so any pointers would be highly appreciated.
Attempt
I initially tried to shoehorn an autogenerated moshi adapter thinking I might be able to build ontop of it but I couldn't figure out what was going on so it didn't really lead me anywhere. But I included the generated code anyway incase it might be useful for the task still.
Edit: This is what I currently have, though im not very pleased by the fact it takes an instance of Moshi to work.
Solution
Generalised approach with a Factory
I tried to port adapter which Eric wrote to kotlin
since I since has realised that a more general approach is better much like
Eric points out in his reply. As soon a this is working, I will re-write parts of this post to make it more understandable, its a bit messy now I apologize for that.
EDIT: I ended up using the solution Eric suggested in another thread but ported to Kotlin.
Adapter with Factory
class SingleToArrayAdapter(
val delegateAdapter: JsonAdapter<List<Any>>,
val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {
companion object {
val INSTANCE = SingleToArrayAdapterFactory()
}
override fun fromJson(reader: JsonReader): Any? =
if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) {
Collections.singletonList(elementAdapter.fromJson(reader))
} else delegateAdapter.fromJson(reader)
override fun toJson(writer: JsonWriter, value: Any?) =
throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects")
class SingleToArrayAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<Any>? {
val delegateAnnotations = Types.nextAnnotations(annotations, SingleToArray::class.java)
if (delegateAnnotations == null) return null
if (Types.getRawType(type) != List::class.java) throw IllegalArgumentException("Only lists may be annotated with @SingleToArray. Found: $type")
val elementType = Types.collectionElementType(type, List::class.java)
val delegateAdapter: JsonAdapter<List<Any>> = moshi.adapter(type, delegateAnnotations)
val elementAdapter: JsonAdapter<Any> = moshi.adapter(elementType)
return SingleToArrayAdapter(delegateAdapter, elementAdapter)
}
}
}
Qualifier
Note: I had to add the Target(FIELD)
.
@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray
Usage
Annotate a field you want to make sure is parsed as a list with @SingleToArray
.
data class Alert(
@SingleToArray
@Json(name = "alert")
val alert: List<Warning>
)
and dont forget to add the adapter to your moshi instance:
val moshi = Moshi.Builder()
.add(SingleToArrayAdapter.INSTANCE)
.build()
Related issues/topics:
- Parse JSON key that is either object or array of object
- Moshi Determine if JSON is array or single object
- https://github.com/square/moshi
android json kotlin adapter moshi
add a comment |
I am building an app which can fetch a Warning
object from an API which I do not control nor do they want to remove this behaivor to which I am trying to work around.
I would like to be able to parse either a single Warning
object or a List<Warning>
objects. (Because the API returns either 1 object if there is only 1 or > 1 a list of objects.)
Question
How can I parse an object as a list directly or parse list this with Moshi?
The data I get looks either like this:
{
# List<Warning>
"warnings": [{...}, {...}]
}
or like this:
{
# Warning
"warnings": {...}
}
I have read and found some sources on this topic, but no examples and I am not sure how to proceed.. so any pointers would be highly appreciated.
Attempt
I initially tried to shoehorn an autogenerated moshi adapter thinking I might be able to build ontop of it but I couldn't figure out what was going on so it didn't really lead me anywhere. But I included the generated code anyway incase it might be useful for the task still.
Edit: This is what I currently have, though im not very pleased by the fact it takes an instance of Moshi to work.
Solution
Generalised approach with a Factory
I tried to port adapter which Eric wrote to kotlin
since I since has realised that a more general approach is better much like
Eric points out in his reply. As soon a this is working, I will re-write parts of this post to make it more understandable, its a bit messy now I apologize for that.
EDIT: I ended up using the solution Eric suggested in another thread but ported to Kotlin.
Adapter with Factory
class SingleToArrayAdapter(
val delegateAdapter: JsonAdapter<List<Any>>,
val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {
companion object {
val INSTANCE = SingleToArrayAdapterFactory()
}
override fun fromJson(reader: JsonReader): Any? =
if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) {
Collections.singletonList(elementAdapter.fromJson(reader))
} else delegateAdapter.fromJson(reader)
override fun toJson(writer: JsonWriter, value: Any?) =
throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects")
class SingleToArrayAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<Any>? {
val delegateAnnotations = Types.nextAnnotations(annotations, SingleToArray::class.java)
if (delegateAnnotations == null) return null
if (Types.getRawType(type) != List::class.java) throw IllegalArgumentException("Only lists may be annotated with @SingleToArray. Found: $type")
val elementType = Types.collectionElementType(type, List::class.java)
val delegateAdapter: JsonAdapter<List<Any>> = moshi.adapter(type, delegateAnnotations)
val elementAdapter: JsonAdapter<Any> = moshi.adapter(elementType)
return SingleToArrayAdapter(delegateAdapter, elementAdapter)
}
}
}
Qualifier
Note: I had to add the Target(FIELD)
.
@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray
Usage
Annotate a field you want to make sure is parsed as a list with @SingleToArray
.
data class Alert(
@SingleToArray
@Json(name = "alert")
val alert: List<Warning>
)
and dont forget to add the adapter to your moshi instance:
val moshi = Moshi.Builder()
.add(SingleToArrayAdapter.INSTANCE)
.build()
Related issues/topics:
- Parse JSON key that is either object or array of object
- Moshi Determine if JSON is array or single object
- https://github.com/square/moshi
android json kotlin adapter moshi
I am building an app which can fetch a Warning
object from an API which I do not control nor do they want to remove this behaivor to which I am trying to work around.
I would like to be able to parse either a single Warning
object or a List<Warning>
objects. (Because the API returns either 1 object if there is only 1 or > 1 a list of objects.)
Question
How can I parse an object as a list directly or parse list this with Moshi?
The data I get looks either like this:
{
# List<Warning>
"warnings": [{...}, {...}]
}
or like this:
{
# Warning
"warnings": {...}
}
I have read and found some sources on this topic, but no examples and I am not sure how to proceed.. so any pointers would be highly appreciated.
Attempt
I initially tried to shoehorn an autogenerated moshi adapter thinking I might be able to build ontop of it but I couldn't figure out what was going on so it didn't really lead me anywhere. But I included the generated code anyway incase it might be useful for the task still.
Edit: This is what I currently have, though im not very pleased by the fact it takes an instance of Moshi to work.
Solution
Generalised approach with a Factory
I tried to port adapter which Eric wrote to kotlin
since I since has realised that a more general approach is better much like
Eric points out in his reply. As soon a this is working, I will re-write parts of this post to make it more understandable, its a bit messy now I apologize for that.
EDIT: I ended up using the solution Eric suggested in another thread but ported to Kotlin.
Adapter with Factory
class SingleToArrayAdapter(
val delegateAdapter: JsonAdapter<List<Any>>,
val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {
companion object {
val INSTANCE = SingleToArrayAdapterFactory()
}
override fun fromJson(reader: JsonReader): Any? =
if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) {
Collections.singletonList(elementAdapter.fromJson(reader))
} else delegateAdapter.fromJson(reader)
override fun toJson(writer: JsonWriter, value: Any?) =
throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects")
class SingleToArrayAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<Any>? {
val delegateAnnotations = Types.nextAnnotations(annotations, SingleToArray::class.java)
if (delegateAnnotations == null) return null
if (Types.getRawType(type) != List::class.java) throw IllegalArgumentException("Only lists may be annotated with @SingleToArray. Found: $type")
val elementType = Types.collectionElementType(type, List::class.java)
val delegateAdapter: JsonAdapter<List<Any>> = moshi.adapter(type, delegateAnnotations)
val elementAdapter: JsonAdapter<Any> = moshi.adapter(elementType)
return SingleToArrayAdapter(delegateAdapter, elementAdapter)
}
}
}
Qualifier
Note: I had to add the Target(FIELD)
.
@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray
Usage
Annotate a field you want to make sure is parsed as a list with @SingleToArray
.
data class Alert(
@SingleToArray
@Json(name = "alert")
val alert: List<Warning>
)
and dont forget to add the adapter to your moshi instance:
val moshi = Moshi.Builder()
.add(SingleToArrayAdapter.INSTANCE)
.build()
Related issues/topics:
- Parse JSON key that is either object or array of object
- Moshi Determine if JSON is array or single object
- https://github.com/square/moshi
android json kotlin adapter moshi
android json kotlin adapter moshi
edited Nov 26 '18 at 12:24
sphrak
asked Nov 16 '18 at 19:12
sphraksphrak
6818
6818
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
the API returns either 1 object if there is only 1 or > 1 a list of objects.
Create an adapter that peeks to see if you're getting an array first.
Here is exactly what you want. It includes a qualifier, so you can apply it only to lists that may have this behavior for single items. @SingleToArray List<Warning>
.
There is another example of dealing with multiple formats here for further reading.
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53344033%2fmoshi-parse-single-object-or-list-of-objects-kotlin%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
the API returns either 1 object if there is only 1 or > 1 a list of objects.
Create an adapter that peeks to see if you're getting an array first.
Here is exactly what you want. It includes a qualifier, so you can apply it only to lists that may have this behavior for single items. @SingleToArray List<Warning>
.
There is another example of dealing with multiple formats here for further reading.
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
add a comment |
the API returns either 1 object if there is only 1 or > 1 a list of objects.
Create an adapter that peeks to see if you're getting an array first.
Here is exactly what you want. It includes a qualifier, so you can apply it only to lists that may have this behavior for single items. @SingleToArray List<Warning>
.
There is another example of dealing with multiple formats here for further reading.
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
add a comment |
the API returns either 1 object if there is only 1 or > 1 a list of objects.
Create an adapter that peeks to see if you're getting an array first.
Here is exactly what you want. It includes a qualifier, so you can apply it only to lists that may have this behavior for single items. @SingleToArray List<Warning>
.
There is another example of dealing with multiple formats here for further reading.
the API returns either 1 object if there is only 1 or > 1 a list of objects.
Create an adapter that peeks to see if you're getting an array first.
Here is exactly what you want. It includes a qualifier, so you can apply it only to lists that may have this behavior for single items. @SingleToArray List<Warning>
.
There is another example of dealing with multiple formats here for further reading.
answered Nov 19 '18 at 2:37
Eric CochranEric Cochran
4,59853363
4,59853363
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
add a comment |
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Eric, thank you for your reply -- the links you posted is indeed what I am looking for and I did end up with something "like that", however it feels like im still missing something. I have updated my answer with current adapter code im using, it works but it feels dirty.. what do you think?
– sphrak
Nov 19 '18 at 7:46
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
Use a JsonAdapter.Factory, and you won't need to pass in the Moshi instance.
– Eric Cochran
Nov 19 '18 at 11:16
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I have tried to use a Factory to avoid the moshi instance but somehow that completely changed the behaivor of the adapter but I think I am close, im probably just missing something trivial. I added the code for reference meanwhile.
– sphrak
Nov 21 '18 at 15:24
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
I did get it to work eventually, forgot to clean the project inbetween builds. I thank you and have also accepted your answer since it was what I eventually ended up using :)
– sphrak
Nov 26 '18 at 10:49
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53344033%2fmoshi-parse-single-object-or-list-of-objects-kotlin%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown