# Literal로 crdt.Object 생성하기 스팩 https://codepair.yorkie.dev/fduqs ## json.Array 생성 --- #### 1. SetNewArray method를 이용한 빈 배열 생성 ```go= root.SetNewArray("k1") ``` #### 2. 특정 타입(user defined struct 포함)으로 만들어진 slice, array로 json.Array 선언 > primitive, interface{ }, Counter, Text, Tree, []map[string]interface{ }, user defined struct ``` go= // int root.SetNewArray("k1", []int{0, 1, 2}) // void interface root.SetNewArray("k2", []interface{}{0,1,2}) // Counter cnt := json.NewCounter(0, crdt.LongCnt) root.SetNewArray("counters", []json.Counter{cnt, cnt, cnt}) // User defined struct type T1 struct { msg string Msg string } root.SetNewArray("structs", []T1{{"hello", "Hello"}, {"world", "World"}}) // typed slice in typed slice arr := []int{1, 2, 3} root.SetNewArray("arrays", []([]int){arr, arr}) // typed slice in typed array arr := [3]int{1, 2, 3} root.SetNewArray("arrays", []([3]int){arr}) ``` > 현재 array 같은 경우 slice로 변환되어 처리된다. 성능상 오버헤드가 있을 수 있다. ## json.Object 생성 --- #### 1. SetNewObject method를 이용한 빈 객체 생성 ```go= root.SetNewObject("obj") ``` #### 2. map을 이용해서 json object 생성 ```go= root.SetNewObject("obj", map[string]interface{}{ // 1: object "str": "v", // 1: string "arr": []interface{}{1, "str"}, // 3: array, 1, "str" "obj": map[string]interface{}{"key3": 42.2}, // 2: object, 42.2 "cnt": json.NewCounter(0, crdt.LongCnt), // 1: counter "txt": json.NewText(), // 1: text "tree": json.NewTree(&json.TreeNode{ // 1: tree Type: "doc", Children: []json.TreeNode{{ Type: "p", Children: []json.TreeNode{{Type: "text", Value: "ab"}}, }}, }), }) ``` #### 3. user defined struct 로 json object 생성 ```go root.SetNewObject("obj", struct{ M string }{M: text}) ``` - exposed field만 json.Object에 추가된다. > struct와 map 형태는 주소 또한 받을 수 있다. ## Struct field tag --- - user defined struct로 json.Object, json.Array를 초기화 할 때, tag를 이용할 수 있다. - tag는 아래의 포맷을 따른다. 콤마를 기준으로 앞은 name, 뒤는 option이다. ```format `yorkie:(user defined name || - ),options` ``` > name에 대해서는 trim 작업 x -> 공백 허용 options는 trim을 통해 공백 제거 ```go= type Temp struct{ // user defined name, - K1 string `yorkie:"-"` // case1 K2 string `yorkie:"k2"` // case2 // options K3 string `yorkie:",omitEmpty"` // case3 } ``` ### 1. - always omit - `-` 로 태그된 필드는 crdt.Object에 포함되지 않는다. - 뒤에 옵션이 붙으면 -를 user defined name으로 간주 ```go= struct { M1 string `yorkie:"-"` M2 string }{M1: str, M2: str} ``` ```shell= {"obj":{"M2":"foo"}} ``` ### 2. field name mapping - 기본적으로 exposed만 변환하기에 키가 대문자로 시작한다. - 태그를 이용해 미리 정의된 이름으로 key 설정 가능하다. ```go= struct { M1 string `yorkie:"m1"` M2 string `yorkie:"m2"` {M1: str, M2: str} ``` ```shell= {"obj":{"m1":"foo","m2":"foo"}} ``` ### 3. omitEmpty - 값이 없다면 출력되지 않음(초기화를 안해 zero value인 케이스) - nil이 아닌 빈 배열인 경우도 생략 > "tagName,omitEmpty" 는 정상 작동 > "tagName, omitEmpty" 한 칸 뛰어쓰면 작동 안함 (json marshal 마찬가지) > trim 처리로 해결 ```go= // zero value struct { M1 string `yorkie:",omitEmpty"` M2 string `yorkie:",omitEmpty"` }{M1: str} ``` ```shell= {"obj":{"M1":"foo"}} ``` ```go= // empty array struct { M1 []int `yorkie:",omitEmpty"` }{M1: []int{}} ``` ```shell= {"obj":{}} ``` ## 추가 기능 후보 --- - case4 // Counter tag rather than NewCounter ❔ - int값을 던져주고, 이를 Counter 객체로 만들도록 - ( string 값을 던져주고, 이를 Text 객체로 ) / 기존 string을 받아 text를 초기화 생성하는 방법이 없기에 같이 다루어야한다. - case5 // int, float -> string 으로 전환 - int 값을 넣었으나, 인코딩할 때 스트링으로 전환 > sometimes used when communicating with JavaScript programs https://pkg.go.dev/encoding/json#Marshal ## Nil, Zerovalue, Empty 스펙 --- - zero value - 기본적으로 struct의 field 값을 assign 하지 않으면 default값이 들어간다. - nil 은 포인터, 인터페이스, 맵, 슬라이스, 채널, 함수의 zero value이다. - nil slice -> null - empty slice -> [ ] ```go {"zeroValue int struct", struct{ M int }{}, `{"obj":{"M":0}}`}, {"zeroValue string struct", struct{ M string }{}, `{"obj":{"M":""}}`}, ``` ```go {"zeroValue bytes struct", struct{ M []byte }{M: nil}, `{"obj":{"M":""}}`}, {"empty bytes struct", struct{ M []byte }{M: []byte{}}, `{"obj":{"M":""}}`}, 바이트 배열인 경우 nil과 empty 모두 ""으로 표현된다. {"zeroValue array struct", struct{ M []int }{M: nil}, `{"obj":{"M":[]}}`}, {"empty array struct", struct{ M []int }{M: []int{}}, `{"obj":{"M":[]}}`}, ``` 빈 배열과 nil 배열 모두 [ ]으로 표현된다. - 다음은 구조체에서 각 필드를 선언했을 때, 기본 값이 다뤄지는 방법을 정리 | Type | zero value(not initialized) | Empty value(initialized but empty) | Initialize method | | --------------- | --------------- | --------------- | --------------- | | int | 0 | | String | "" | "" |"" | []byte | "" | "" | []byte | json.Array | [ ] | [ ] | []int{} | json.Counter | 0 | | NewCounter| | json.Tree | {"type":"root","children":[]} | {"type":"root","children":[]} | NewTree | json.Text | [ ] | [ ] | NewText | json.Object | { } | | | pointer | null | | nil > Blank means there is no way ## Pointer 스펙 --- - slice, array나 struct안 포인터는 포인터가 가리키는 객체를 생성한다. - 다만 json.Text, Counter, Tree, Object는 지원하지 않는다. (주소를 얻는 것이 불가능) ```go= // available {"struct pointers", []*T1{&t1, &t1}, `[{"M":"a"},{"M":"a"}]`, 5}, {"array pointers", []*[3]int{&arr, &arr}, `[[1,2,3],[1,2,3]]`, 9}, // does not support {"Counter Pointer", []*json.Counter{&json.NewCounter(1, crdt.LongCnt)}, `[1]`} ``` - 포인터가 nil이라면 json.primitive의 null로 바뀌어 생성된다. ```go= {"nested &struct with nil", struct{ M *T1 }{M: nil}, `{"obj":{"M":null}}`, 2}, ``` - json.Object 생성 시 struct와 map 형태는 주소 또한 받을 수 있다. - 마찬가지로 값으로 바꾸어 json.Object가 생성된다. ## Unsupported Values --- If these values are included in the structure array, panic will occur. ```go= pointerCycle, pointerCycleIndirect, mapCycle, sliceCycle, recursiveSliceCycle, ```