Search code examples
jsonscalaarraylistcirce

Add nested fields to a JSON using Circe


Problem

How can I add fields to a Json object if the field is nested?

This is similar to this forum: Adding field to a JSON using Circe

but instead of:

{
  ExistingField: {},
  "Newfield" : {}
}

my end goal is something like:

{
  ExistingField: {},
  "A" : {
    "B" : {
      "C" : "myStringValue"
    }
  },
  "AA" : {
    "BB" : "myStringValue" 
  }
}

Tried

I have a list of nested fields of type String that I would like to iterate through and add.
Instead of them being nested, I just got:

{
  ExistingField: {},
  "A.B.C" : "myStringValue"
}

my list of fields look like:

val listOfFields: List[String] = List("A.B.C", "AA.BB")

EDIT: What if the added field is an array? for example:

{
  ExistingField: {},
  "A" : {
    "B" : {
      "C" : "myStringValue"
    }
  },
  "AA" : {
    "BB" : [
      {
        "CC": "myStringValue"
      },
      {
        "CC": "myStringValue"
      }
    ]
  }
}

Solution

  • The recursive function uses pattern matching to convert a list of subfields (from splitting fields by the delimiter) into a nested Json which can then be deepMerged into the original Json. The foldLeft does this repeatedly for each field originally listed.

    import io.circe.generic.auto._
    import io.circe.parser
    import io.circe.syntax._
    import io.circe.Json
    
    val jsonStr = """{"Fieldalreadythere": {}}"""
    
    val jsonParse = parser.parse(jsonStr)
    
    
    val listOfFields: List[String] = List("A.B.C", "AA.BB")
    
    val listOfSplitFields = listOfFields.map(_.split("\\.").toList)
    
    
    def makeJson(list: List[String]): Json = {
      list match {
        case h :: Nil => Json.fromFields(List((h, Json.fromString("stringValue"))))
        case h :: t => Json.fromFields(List(h -> makeJson(t)))
        case Nil => Json.fromFields(Nil)
      }
    }
    
    
    val originalJson = jsonParse match {
           case Right(value) => value.asObject
           case Left(error) => throw error
        }
    
    
    val updateJsons = listOfSplitFields.map(makeJson(_))
    updateJsons.foldLeft(originalJson.asJson)(_.deepMerge(_))
    

    Result (of type io.circe.Json):

    {
      "AA" : {
        "BB" : "stringValue"
      },
      "A" : {
        "B" : {
          "C" : "stringValue"
        }
      },
      "Fieldalreadythere" : {
    
      }
    }