# Custom config endpoint (either HOCON or JSON) - json type is string instead of object

**URL:** <https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80>\
**Category:** tapir\
**Created:** [January 6, 2023, 7:43am UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80 "2023-01-06T07:43:06Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![hochgi](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/hochgi/32/32_2.png) [@hochgi](https://softwaremill.community/u/hochgi)\
**Post date:** [January 6, 2023, 7:43am UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/1 "2023-01-06T07:43:06Z")

</div>

We’ve implemented an endpoint exposing our typesafe config object (simplified from our real code):

```auto
// https://github.com/lightbend/config/blob/master/HOCON.md#mime-type
case class Hocon() extends CodecFormat {
  override val mediaType: MediaType = MediaType("application", "hocon")
}

val hoconBody: EndpointIO.Body[String, Config] = EndpointIO.Body(
  bodyType = stringBody.bodyType,
  codec = stringBody.codec.format(Hocon()).map(s => ConfigFactory.parseString(s))(_.root().render()),
  info = EndpointIO.Info.empty)

val parseJSON: String => DecodeResult[Config] = inputString => Try {
  ConfigFactory.parseString(
    inputString,
    ConfigParseOptions
      .defaults()
      .setSyntax(ConfigSyntax.JSON))
}.fold(DecodeResult.Error(inputString, _), DecodeResult.Value.apply)
val formatJSON: Config => String = _.root().render(
  ConfigRenderOptions
    .concise()
    .setJson(true))

```

and:

```auto
// this is probably wrong, but not sure how to fix
val configCustomJsonCodec: JsonCodec[Config] = Codec.json[Config](parseJSON)(formatJSON)(Schema.string[Config])

```

The endpoint itself is defined by (simplified from original):

```auto
val paths: EndpointInput.PathCapture[List[String]] = path[String]
  .name("path")
  .description("Configuration key paths, separated by ','")
  .map(_.split(',').toList)(_.mkString(","))

val configAtPath = customBase
  .name("configAtPath")
  .description("Get configuration for some path(s)")
  .in(paths)
  .get
  .out(oneOfBody(hoconBody, customCodecJsonBody[Config](configCustomJsonCodec)))

```

and logic is implemented with (also simplified):

```auto
val config: Config = …

configAtPath.serverLogicPure(traversePaths(config, _))

def traversePaths(config: Config, paths: List[String]): Either[ManagedError, Config] = {
  val (errors, confs) = paths.partitionWith(getConfig(config, _))
  if (errors.isEmpty) Right(confs.reduce(_ withFallback _))
  else Left(NotFound(errors.mkString("\n")))
}

def getConfig(config: Config, path: String): Either[ManagedError, Config] =
  if (config.hasPath(path)) Right(config.withOnlyPath(path))
  else Left(NotFound(s"'$path' does not exist in configuration."))

```

The thing is, that the resulting schema has responses section that looks like:

```yaml
responses:
  '200':
    description: ''
    content:
      application/hocon:
        schema:
          type: string
      application/json:
        schema:
          type: string

```

And it seems like I should have `type: object` instead of `type: string` for the JSON part, but since I used `Schema.string[Config]` for `configCustomJsonCodec` it’s probably why it’s a string.  
But I couldn’t figure out what to use for a free form JSON object like that.

Is there an easy fix I’m missing here?  
Thanks!

---

<div class="post-metadata">

**Author:** ![adamw](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/adamw/32/28_2.png) [@adamw](https://softwaremill.community/u/adamw)\
**Post date:** [January 9, 2023, 11:38am UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/2 "2023-01-09T11:38:23Z")

</div>

This looks correct 🙂 For the `hoconBody` you could probalby use `stringBodyUtf8AnyFormat` and just provide the codec. `configCustomJsonCodec` also is correct, that’s what the `Codec.json` method is for.

A schema for a free-form JSON object is defined as: `Schema(SProduct(Nil), None)`. If you take a look at the [integrations source](https://github.com/softwaremill/tapir/blob/c2284d00ee3200a1d686cd14eac48b044e4b61fd/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala#L49-L55), there are schemas for both arbitrary-json and arbitrary-json-object, which maybe can provide some guidance.

---

<div class="post-metadata">

**Author:** ![hochgi](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/hochgi/32/32_2.png) [@hochgi](https://softwaremill.community/u/hochgi)\
**Post date:** [January 10, 2023, 6:42am UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/3 "2023-01-10T06:42:02Z")

</div>

Thanks @adamw,  
I’m not convinced I got it right though. I tried something similar to what you suggest:

```auto
val schemaForConfigJsonObject: Schema[Config] =
  Schema(SCoproduct(Nil, None)(_ => None), None)

// used schemaForConfigJsonObject instead of Schema.string[Config]
val configCustomJsonCodec: JsonCodec[Config] =
  Codec.json[Config](parseJSON)(formatJSON)(schemaForConfigJsonObject)

```

Now the resulting schema I get is:

```yaml
content:
  application/hocon:
    schema:
      type: string
  application/json:
    schema: {}

```

but according to [docs](https://swagger.io/docs/specification/data-models/data-types/#object) I should aim for:

```yaml
content:
  application/hocon:
    schema:
      type: string
  application/json:
    schema:
      type: object

```

see section on free form objects:

 ![oapi_free-form-object](https://europe1.discourse-cdn.com/flex005/uploads/softwaremill/original/1X/09d00486011a790cb80f914763d831168613305a.png)

as for `schema: {}`, this means “any type”:

 ![oapi_any-type](https://europe1.discourse-cdn.com/flex005/uploads/softwaremill/original/1X/750229809268bd071bc8c6378e5b88ddc9d58815.png)

> A schema without a type matches any data type – numbers, strings, objects, and so on. `{}` is shorthand syntax for an arbitrary-type schema

---

<div class="post-metadata">

**Author:** ![adamw](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/adamw/32/28_2.png) [@adamw](https://softwaremill.community/u/adamw)\
**Post date:** [January 10, 2023, 10:37am UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/4 "2023-01-10T10:37:47Z")

</div>

If you use a coproduct that means it’s an arbitrary JSON - anything goes (as far as JSON syntax allows it). Hence it can be an object, or a number.

`Schema(SProduct(Nil), None)` should be different and produce a schema for a product only.

---

<div class="post-metadata">

**Author:** ![adamw](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/adamw/32/28_2.png) [@adamw](https://softwaremill.community/u/adamw)\
**Post date:** [January 10, 2023, 10:42am UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/5 "2023-01-10T10:42:27Z")

</div>

I tried generating docs for:

```auto
endpoint.get.in(customCodecJsonBody(Codec.json[Any](_ => ???)(_ => ???)(Schema(SchemaType.SProduct(Nil), None))))

```

and I think I got the correct result:

```auto
paths:
  /:
    get:
      operationId: getRoot
      requestBody:
        content:
          application/json:
            schema:
              type: object
        required: true

```

---

<div class="post-metadata">

**Author:** ![hochgi](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/hochgi/32/32_2.png) [@hochgi](https://softwaremill.community/u/hochgi)\
**Post date:** [January 10, 2023, 12:10pm UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/6 "2023-01-10T12:10:29Z")

</div>

Thanks @adamw ! worked like a charm!  
Thank you so so much!!!

---

<div class="post-metadata">

**Author:** ![adamw](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/adamw/32/28_2.png) [@adamw](https://softwaremill.community/u/adamw)\
**Post date:** [January 10, 2023, 2:21pm UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/7 "2023-01-10T14:21:25Z")

</div>

I created a PR adding this as a method to `Schema` and replacing the existing usages: [Schemas for any / any object by adamw · Pull Request #2673 · softwaremill/tapir · GitHub](https://github.com/softwaremill/tapir/pull/2673)

Should be easier now 🙂

---

<div class="post-metadata">

**Author:** ![hochgi](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/hochgi/32/32_2.png) [@hochgi](https://softwaremill.community/u/hochgi)\
**Post date:** [January 10, 2023, 3:23pm UTC](https://softwaremill.community/t/custom-config-endpoint-either-hocon-or-json-json-type-is-string-instead-of-object/80/8 "2023-01-10T15:23:37Z")

</div>

awesome!!!  
thank you!!!
